| """ |
| theme.py |
| Single source of truth for InsightUX's visual design system: CSS custom |
| properties (dark + light), a handful of shared utility classes, an inline |
| icon set, and the theme-toggle/persistence script. |
| |
| Every InsightUX UI surface is a separate HTML document or an independently |
| injected JS bundle (landing page, toolbar/sidebar overlay, session report, |
| mouse-tracker panel) — none of them share a stylesheet at runtime. This |
| module exists so all four still look like one product: each template |
| splices THEME_CSS + THEME_TOGGLE_JS into its own <style>/<script>, instead |
| of every surface hand-rolling its own palette. |
| |
| No heavy imports (no cv2/mediapipe/pyautogui) so analysis.py can import |
| this standalone, the same way it already runs outside the pywebview app. |
| """ |
|
|
| import json |
|
|
| |
| |
| |
| |
| |
|
|
| ICON_PATHS = { |
| "search": '<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>', |
| "x": '<path d="M18 6 6 18"/><path d="m6 6 12 12"/>', |
| "sun": '<circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"/>', |
| "moon": '<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/>', |
| "eye": '<path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0"/><circle cx="12" cy="12" r="3"/>', |
| "cursor": '<path d="M12.586 12.586 19 19"/><path d="M3.688 3.037a.497.497 0 0 0-.651.651l6.5 15.999a.501.501 0 0 0 .947-.062l1.569-6.083a2 2 0 0 1 1.448-1.479l6.124-1.579a.5.5 0 0 0 .063-.947z"/>', |
| "target": '<circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/>', |
| "play": '<polygon points="6 3 20 12 6 21 6 3"/>', |
| "stop": '<rect width="16" height="16" x="4" y="4" rx="3"/>', |
| "home": '<path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><path d="M9 22V12h6v10"/>', |
| "arrow-left": '<path d="m12 19-7-7 7-7"/><path d="M19 12H5"/>', |
| "arrow-right": '<path d="M5 12h14"/><path d="m12 5 7 7-7 7"/>', |
| "refresh": '<path d="M21 12a9 9 0 1 1-2.64-6.36"/><path d="M21 3v6h-6"/>', |
| "lock": '<rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>', |
| "globe": '<circle cx="12" cy="12" r="10"/><path d="M2 12h20"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10Z"/>', |
| "image": '<rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.1-3.1a2 2 0 0 0-2.8 0L6 21"/>', |
| "type": '<path d="M17 6.1H3"/><path d="M21 12.1H3"/><path d="M15.1 18H3"/>', |
| "video": '<path d="m16 13 5.2 3.5a.5.5 0 0 0 .8-.4V7.9a.5.5 0 0 0-.8-.4L16 10.5"/><rect x="2" y="6" width="14" height="12" rx="2"/>', |
| "click": '<path d="m9 9 5 12 1.8-5.2L21 14Z"/><path d="M7.2 2.2 8 5.1"/><path d="m5.1 8-2.9-.8"/><path d="M14 4.1 12 6"/><path d="m6 12-1.9 2"/>', |
| "download": '<path d="M12 15V3"/><path d="M3 15v4a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-4"/><path d="m7 10 5 5 5-5"/>', |
| "maximize": '<path d="M8 3H5a2 2 0 0 0-2 2v3"/><path d="M21 8V5a2 2 0 0 0-2-2h-3"/><path d="M3 16v3a2 2 0 0 0 2 2h3"/><path d="M16 21h3a2 2 0 0 0 2-2v-3"/>', |
| "sliders": '<line x1="4" x2="4" y1="21" y2="14"/><line x1="4" x2="4" y1="10" y2="3"/><line x1="12" x2="12" y1="21" y2="12"/><line x1="12" x2="12" y1="8" y2="3"/><line x1="20" x2="20" y1="21" y2="16"/><line x1="20" x2="20" y1="12" y2="3"/><line x1="1" x2="7" y1="14" y2="14"/><line x1="9" x2="15" y1="8" y2="8"/><line x1="17" x2="23" y1="16" y2="16"/>', |
| "clock": '<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>', |
| "chevron-down": '<path d="m6 9 6 6 6-6"/>', |
| "chevron-left": '<path d="m15 18-6-6 6-6"/>', |
| "chevron-right": '<path d="m9 18 6-6-6-6"/>', |
| "settings": '<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1Z"/>', |
| "layers": '<path d="m12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83Z"/><path d="m22 12.32-8.58 3.9a2 2 0 0 1-1.66 0L2 12.32"/><path d="m22 17.32-8.58 3.9a2 2 0 0 1-1.66 0L2 17.32"/>', |
| "trash": '<path d="M3 6h18"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>', |
| "info": '<circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/>', |
| "check": '<path d="M20 6 9 17l-5-5"/>', |
| "camera": '<path d="M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3z"/><circle cx="12" cy="13" r="3"/>', |
| "menu": '<line x1="4" x2="20" y1="6" y2="6"/><line x1="4" x2="20" y1="12" y2="12"/><line x1="4" x2="20" y1="18" y2="18"/>', |
| "bar-chart": '<line x1="12" x2="12" y1="20" y2="10"/><line x1="18" x2="18" y1="20" y2="4"/><line x1="6" x2="6" y1="20" y2="16"/>', |
| "trending-up": '<polyline points="22 7 13.5 15.5 8.5 10.5 2 17"/><polyline points="16 7 22 7 22 13"/>', |
| "user": '<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>', |
| "users": '<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>', |
| "log-out": '<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" x2="9" y1="12" y2="12"/>', |
| "more-vertical": '<circle cx="12" cy="12" r="1"/><circle cx="12" cy="5" r="1"/><circle cx="12" cy="19" r="1"/>', |
| "plus": '<path d="M5 12h14"/><path d="M12 5v14"/>', |
| } |
|
|
|
|
| def icon(name, size=18, css_class=""): |
| """Python-side inline <svg> for a template string.""" |
| body = ICON_PATHS.get(name, "") |
| cls = ("iux-icon " + css_class).strip() |
| return ( |
| f'<svg class="{cls}" width="{size}" height="{size}" viewBox="0 0 24 24" ' |
| f'fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" ' |
| f'stroke-linejoin="round">{body}</svg>' |
| ) |
|
|
|
|
| ICONS_JS = ( |
| "const IUX_ICONS = " + json.dumps(ICON_PATHS) + ";\n" |
| "function iuxIcon(name, size){\n" |
| " size = size || 18;\n" |
| " return '<svg class=\"iux-icon\" width=\"' + size + '\" height=\"' + size + '\" " |
| "viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" " |
| "stroke-linecap=\"round\" stroke-linejoin=\"round\">' + (IUX_ICONS[name] || '') + '</svg>';\n" |
| "}\n" |
| ) |
|
|
|
|
| |
| |
| |
| |
| |
|
|
| THEME_CSS = r""" |
| :root { |
| --iux-bg: #1B1918; |
| --iux-bg-alt: #221F1D; |
| --iux-surface: #292522; |
| --iux-surface-hi: #332E2A; |
| --iux-border: rgba(198, 188, 178, 0.16); |
| --iux-text: #F9F8F3; |
| --iux-text-dim: #C6BCB2; |
| --iux-text-faint: #8E8E92; |
| |
| --iux-primary: #FFE9A8; |
| --iux-primary-light: #FFE9A8; |
| --iux-indigo: #FFE9A8; |
| --iux-accent-grad: #FFE9A8; |
| --iux-cyan: #7FE0C8; |
| --iux-lavender: #FFE9A8; |
| --iux-on-accent: #312F2E; |
| |
| --iux-success: #34D399; |
| --iux-warning: #F59E0B; |
| --iux-danger: #FB7185; |
| --iux-info: #38BDF8; |
| |
| --iux-radius-sm: 8px; |
| --iux-radius: 14px; |
| --iux-radius-lg: 20px; |
| --iux-shadow-sm: 0 2px 10px rgba(0,0,0,0.32); |
| --iux-shadow: 0 10px 34px rgba(10,8,4,0.5); |
| --iux-shadow-glow: 0 0 0 1px rgba(198,188,178,0.22), 0 8px 22px rgba(0,0,0,0.35); |
| --iux-blur: blur(18px); |
| --iux-ease: cubic-bezier(0.22, 1, 0.36, 1); |
| --iux-font: -apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', 'Manrope', Arial, sans-serif; |
| |
| --iux-page-bg: |
| radial-gradient(circle at 18% 10%, color-mix(in srgb, var(--iux-primary-light) 16%, transparent), transparent 42%), |
| radial-gradient(circle at 82% 82%, color-mix(in srgb, var(--iux-indigo) 18%, transparent), transparent 45%), |
| var(--iux-bg); |
| } |
| |
| :root[data-theme="light"] { |
| --iux-bg: #FBF7E7; |
| --iux-bg-alt: #F3EBC6; |
| --iux-surface: #F9F8F3; |
| --iux-surface-hi: #E6E7E4; |
| --iux-border: rgba(49, 47, 46, 0.10); |
| --iux-text: #312F2E; |
| --iux-text-dim: #747579; |
| --iux-text-faint: #8E8E92; |
| |
| --iux-primary: #D9A400; |
| --iux-primary-light: #D9A400; |
| --iux-indigo: #D9A400; |
| --iux-accent-grad: #D9A400; |
| --iux-lavender: #B8860B; |
| |
| --iux-shadow-sm: 0 2px 10px rgba(49,47,46,0.08); |
| --iux-shadow: 0 14px 34px rgba(49,47,46,0.12); |
| --iux-shadow-glow: 0 0 0 1px rgba(49,47,46,0.14), 0 8px 18px rgba(49,47,46,0.10); |
| |
| --iux-page-bg: |
| linear-gradient(120deg, #E6E7E4 0%, #F9F8F3 45%, #FBF7E7 75%), |
| radial-gradient(circle at 88% 18%, color-mix(in srgb, #F3EBC6 90%, transparent), transparent 58%); |
| } |
| |
| .iux-icon { display: inline-block; vertical-align: middle; flex-shrink: 0; } |
| |
| .iux-btn { |
| display: inline-flex; align-items: center; justify-content: center; gap: 6px; |
| border-radius: var(--iux-radius-sm); border: 1px solid var(--iux-border); |
| background: var(--iux-surface); color: var(--iux-text); cursor: pointer; |
| font: 500 12.5px var(--iux-font); |
| transition: transform 0.15s var(--iux-ease), background 0.15s var(--iux-ease), box-shadow 0.15s var(--iux-ease); |
| } |
| .iux-btn:hover { background: var(--iux-surface-hi); transform: translateY(-1px); } |
| .iux-btn:active { transform: translateY(0) scale(0.97); } |
| .iux-btn.primary { background: var(--iux-accent-grad); border-color: transparent; color: #fff; } |
| .iux-btn.primary:hover { box-shadow: var(--iux-shadow-glow); } |
| |
| .iux-card { |
| background: var(--iux-surface); border: 1px solid var(--iux-border); |
| border-radius: var(--iux-radius); box-shadow: var(--iux-shadow-sm); |
| transition: transform 0.18s var(--iux-ease), box-shadow 0.18s var(--iux-ease); |
| } |
| .iux-card.hoverable:hover { transform: translateY(-2px); box-shadow: var(--iux-shadow); } |
| |
| .iux-glass { |
| background: color-mix(in srgb, var(--iux-surface) 82%, transparent); |
| backdrop-filter: var(--iux-blur); -webkit-backdrop-filter: var(--iux-blur); |
| border: 1px solid var(--iux-border); |
| } |
| |
| .iux-pill { |
| display: inline-flex; align-items: center; gap: 6px; |
| padding: 5px 12px; border-radius: 999px; font: 600 11px var(--iux-font); |
| border: 1px solid var(--iux-border); background: var(--iux-surface-hi); color: var(--iux-text-dim); |
| white-space: nowrap; |
| } |
| .iux-pill .dot { |
| width: 7px; height: 7px; border-radius: 50%; background: var(--iux-text-faint); flex-shrink: 0; |
| } |
| .iux-pill.on { color: var(--iux-success); border-color: rgba(52,211,153,0.35); background: rgba(52,211,153,0.1); } |
| .iux-pill.on .dot { background: var(--iux-success); animation: iux-pulse 1.6s ease-in-out infinite; } |
| |
| @keyframes iux-pulse { |
| 0%, 100% { box-shadow: 0 0 0 0 rgba(52,211,153,0.55); } |
| 70% { box-shadow: 0 0 0 6px rgba(52,211,153,0); } |
| } |
| |
| .iux-toast { |
| display: flex; align-items: center; gap: 8px; padding: 10px 14px; |
| border-radius: var(--iux-radius-sm); background: var(--iux-surface); color: var(--iux-text); |
| border: 1px solid var(--iux-border); box-shadow: var(--iux-shadow); |
| font: 500 12.5px var(--iux-font); |
| animation: iux-toast-in 0.25s var(--iux-ease); |
| } |
| @keyframes iux-toast-in { |
| from { opacity: 0; transform: translateY(-6px) scale(0.98); } |
| to { opacity: 1; transform: translateY(0) scale(1); } |
| } |
| |
| .iux-fade-in { animation: iux-fade 0.35s var(--iux-ease); } |
| @keyframes iux-fade { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } } |
| |
| ::-webkit-scrollbar { width: 9px; height: 9px; } |
| ::-webkit-scrollbar-track { background: transparent; } |
| ::-webkit-scrollbar-thumb { background: var(--iux-border); border-radius: 8px; } |
| ::-webkit-scrollbar-thumb:hover { background: var(--iux-text-faint); } |
| """ |
|
|
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| THEME_TOGGLE_JS = r""" |
| (function(){ |
| function readPrefs(){ |
| try { |
| if (window.name && window.name.indexOf('__iuxprefs__:') === 0) { |
| return JSON.parse(window.name.slice(13)); |
| } |
| } catch (e) {} |
| try { |
| const raw = localStorage.getItem('__iux_prefs__'); |
| if (raw) return JSON.parse(raw); |
| } catch (e) {} |
| return {}; |
| } |
| function writePrefs(prefs){ |
| try { window.name = '__iuxprefs__:' + JSON.stringify(prefs); } catch (e) {} |
| try { localStorage.setItem('__iux_prefs__', JSON.stringify(prefs)); } catch (e) {} |
| } |
| const prefs = readPrefs(); |
| const theme = prefs.theme === 'dark' ? 'dark' : 'light'; |
| document.documentElement.setAttribute('data-theme', theme); |
| |
| window.insightuxGetPrefs = readPrefs; |
| window.insightuxSetPref = function(key, value){ |
| const p = readPrefs(); |
| p[key] = value; |
| writePrefs(p); |
| }; |
| window.insightuxGetTheme = function(){ return readPrefs().theme === 'dark' ? 'dark' : 'light'; }; |
| window.insightuxToggleTheme = function(){ |
| const next = window.insightuxGetTheme() === 'light' ? 'dark' : 'light'; |
| document.documentElement.setAttribute('data-theme', next); |
| window.insightuxSetPref('theme', next); |
| return next; |
| }; |
| })(); |
| """ |
|
|