import { html, css, LitElement } from '../assets/lit-core-2.7.4.min.js'; export class MainHeader extends LitElement { static properties = { isSessionActive: { type: Boolean, state: true }, shortcuts: { type: Object, state: true }, }; static styles = css` :host { display: flex; transform: translate3d(0, 0, 0); backface-visibility: hidden; transition: transform 0.2s cubic-bezier(0.23, 1, 0.32, 1), opacity 0.2s ease-out; will-change: transform, opacity; } :host(.hiding) { animation: slideUp 0.3s cubic-bezier(0.4, 0, 0.6, 1) forwards; } :host(.showing) { animation: slideDown 0.35s cubic-bezier(0.34, 1.56, 0.64, 1) forwards; } :host(.sliding-in) { animation: fadeIn 0.2s ease-out forwards; } :host(.hidden) { opacity: 0; transform: translateY(-150%) scale(0.85); pointer-events: none; } @keyframes slideUp { 0% { opacity: 1; transform: translateY(0) scale(1); filter: blur(0px); } 30% { opacity: 0.7; transform: translateY(-20%) scale(0.98); filter: blur(0.5px); } 70% { opacity: 0.3; transform: translateY(-80%) scale(0.92); filter: blur(1.5px); } 100% { opacity: 0; transform: translateY(-150%) scale(0.85); filter: blur(2px); } } @keyframes slideDown { 0% { opacity: 0; transform: translateY(-150%) scale(0.85); filter: blur(2px); } 30% { opacity: 0.5; transform: translateY(-50%) scale(0.92); filter: blur(1px); } 65% { opacity: 0.9; transform: translateY(-5%) scale(0.99); filter: blur(0.2px); } 85% { opacity: 0.98; transform: translateY(2%) scale(1.005); filter: blur(0px); } 100% { opacity: 1; transform: translateY(0) scale(1); filter: blur(0px); } } @keyframes fadeIn { 0% { opacity: 0; } 100% { opacity: 1; } } * { font-family: 'Helvetica Neue', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; cursor: default; user-select: none; } .header { width: max-content; height: 47px; padding: 2px 10px 2px 13px; background: transparent; overflow: hidden; border-radius: 9000px; /* backdrop-filter: blur(1px); */ justify-content: space-between; align-items: center; display: inline-flex; box-sizing: border-box; position: relative; } .header::before { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.6); border-radius: 9000px; z-index: -1; } .header::after { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; border-radius: 9000px; padding: 1px; background: linear-gradient(169deg, rgba(255, 255, 255, 0.17) 0%, rgba(255, 255, 255, 0.08) 50%, rgba(255, 255, 255, 0.17) 100%); -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); -webkit-mask-composite: destination-out; mask-composite: exclude; pointer-events: none; } .listen-button { height: 26px; padding: 0 13px; background: transparent; border-radius: 9000px; justify-content: center; width: 78px; align-items: center; gap: 6px; display: flex; border: none; cursor: pointer; position: relative; } .listen-button.active::before { background: rgba(215, 0, 0, 0.5); } .listen-button.active:hover::before { background: rgba(255, 20, 20, 0.6); } .listen-button:hover::before { background: rgba(255, 255, 255, 0.18); } .listen-button::before { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(255, 255, 255, 0.14); border-radius: 9000px; z-index: -1; transition: background 0.15s ease; } .listen-button::after { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; border-radius: 9000px; padding: 1px; background: linear-gradient(169deg, rgba(255, 255, 255, 0.17) 0%, rgba(255, 255, 255, 0.08) 50%, rgba(255, 255, 255, 0.17) 100%); -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); -webkit-mask-composite: destination-out; mask-composite: exclude; pointer-events: none; } .header-actions { height: 26px; box-sizing: border-box; justify-content: flex-start; align-items: center; gap: 9px; display: flex; padding: 0 8px; border-radius: 6px; transition: background 0.15s ease; } .header-actions:hover { background: rgba(255, 255, 255, 0.1); } .ask-action { margin-left: 4px; } .action-button, .action-text { padding-bottom: 1px; justify-content: center; align-items: center; gap: 10px; display: flex; } .action-text-content { color: white; font-size: 12px; font-family: 'Helvetica Neue', sans-serif; font-weight: 500; /* Medium */ word-wrap: break-word; } .icon-container { justify-content: flex-start; align-items: center; gap: 4px; display: flex; } .icon-container.ask-icons svg, .icon-container.showhide-icons svg { width: 12px; height: 12px; } .listen-icon svg { width: 12px; height: 11px; position: relative; top: 1px; } .icon-box { color: white; font-size: 12px; font-family: 'Helvetica Neue', sans-serif; font-weight: 500; background-color: rgba(255, 255, 255, 0.1); border-radius: 13%; width: 18px; height: 18px; display: flex; align-items: center; justify-content: center; } .settings-button { padding: 5px; border-radius: 50%; background: transparent; transition: background 0.15s ease; color: white; border: none; cursor: pointer; display: flex; align-items: center; gap: 6px; } .settings-button:hover { background: rgba(255, 255, 255, 0.1); } .settings-icon { display: flex; align-items: center; justify-content: center; padding: 3px; } .settings-icon svg { width: 16px; height: 16px; } /* ────────────────[ GLASS BYPASS ]─────────────── */ :host-context(body.has-glass) .header, :host-context(body.has-glass) .listen-button, :host-context(body.has-glass) .header-actions, :host-context(body.has-glass) .settings-button { background: transparent !important; filter: none !important; box-shadow: none !important; backdrop-filter: none !important; } :host-context(body.has-glass) .icon-box { background: transparent !important; border: none !important; } :host-context(body.has-glass) .header::before, :host-context(body.has-glass) .header::after, :host-context(body.has-glass) .listen-button::before, :host-context(body.has-glass) .listen-button::after { display: none !important; } :host-context(body.has-glass) .header-actions:hover, :host-context(body.has-glass) .settings-button:hover, :host-context(body.has-glass) .listen-button:hover::before { background: transparent !important; } :host-context(body.has-glass) * { animation: none !important; transition: none !important; transform: none !important; filter: none !important; backdrop-filter: none !important; box-shadow: none !important; } :host-context(body.has-glass) .header, :host-context(body.has-glass) .listen-button, :host-context(body.has-glass) .header-actions, :host-context(body.has-glass) .settings-button, :host-context(body.has-glass) .icon-box { border-radius: 0 !important; } :host-context(body.has-glass) { animation: none !important; transition: none !important; transform: none !important; will-change: auto !important; } `; constructor() { super(); this.shortcuts = {}; this.dragState = null; this.wasJustDragged = false; this.isVisible = true; this.isAnimating = false; this.hasSlidIn = false; this.settingsHideTimer = null; this.isSessionActive = false; this.animationEndTimer = null; this.handleMouseMove = this.handleMouseMove.bind(this); this.handleMouseUp = this.handleMouseUp.bind(this); this.handleAnimationEnd = this.handleAnimationEnd.bind(this); } async handleMouseDown(e) { e.preventDefault(); const { ipcRenderer } = window.require('electron'); const initialPosition = await ipcRenderer.invoke('get-header-position'); this.dragState = { initialMouseX: e.screenX, initialMouseY: e.screenY, initialWindowX: initialPosition.x, initialWindowY: initialPosition.y, moved: false, }; window.addEventListener('mousemove', this.handleMouseMove, { capture: true }); window.addEventListener('mouseup', this.handleMouseUp, { once: true, capture: true }); } handleMouseMove(e) { if (!this.dragState) return; const deltaX = Math.abs(e.screenX - this.dragState.initialMouseX); const deltaY = Math.abs(e.screenY - this.dragState.initialMouseY); if (deltaX > 3 || deltaY > 3) { this.dragState.moved = true; } const newWindowX = this.dragState.initialWindowX + (e.screenX - this.dragState.initialMouseX); const newWindowY = this.dragState.initialWindowY + (e.screenY - this.dragState.initialMouseY); const { ipcRenderer } = window.require('electron'); ipcRenderer.invoke('move-header-to', newWindowX, newWindowY); } handleMouseUp(e) { if (!this.dragState) return; const wasDragged = this.dragState.moved; window.removeEventListener('mousemove', this.handleMouseMove, { capture: true }); this.dragState = null; if (wasDragged) { this.wasJustDragged = true; setTimeout(() => { this.wasJustDragged = false; }, 0); } } toggleVisibility() { if (this.isAnimating) { console.log('[MainHeader] Animation already in progress, ignoring toggle'); return; } if (this.animationEndTimer) { clearTimeout(this.animationEndTimer); this.animationEndTimer = null; } this.isAnimating = true; if (this.isVisible) { this.hide(); } else { this.show(); } } hide() { this.classList.remove('showing', 'hidden'); this.classList.add('hiding'); this.isVisible = false; this.animationEndTimer = setTimeout(() => { if (this.classList.contains('hiding')) { this.handleAnimationEnd({ target: this }); } }, 350); } show() { this.classList.remove('hiding', 'hidden'); this.classList.add('showing'); this.isVisible = true; this.animationEndTimer = setTimeout(() => { if (this.classList.contains('showing')) { this.handleAnimationEnd({ target: this }); } }, 400); } handleAnimationEnd(e) { if (e.target !== this) return; if (this.animationEndTimer) { clearTimeout(this.animationEndTimer); this.animationEndTimer = null; } this.isAnimating = false; if (this.classList.contains('hiding')) { this.classList.remove('hiding'); this.classList.add('hidden'); if (window.require) { const { ipcRenderer } = window.require('electron'); ipcRenderer.send('header-animation-complete', 'hidden'); } } else if (this.classList.contains('showing')) { this.classList.remove('showing'); if (window.require) { const { ipcRenderer } = window.require('electron'); ipcRenderer.send('header-animation-complete', 'visible'); } } else if (this.classList.contains('sliding-in')) { this.classList.remove('sliding-in'); this.hasSlidIn = true; console.log('[MainHeader] Slide-in animation completed'); } } startSlideInAnimation() { if (this.hasSlidIn) return; this.classList.add('sliding-in'); } connectedCallback() { super.connectedCallback(); this.addEventListener('animationend', this.handleAnimationEnd); if (window.require) { const { ipcRenderer } = window.require('electron'); this._sessionStateListener = (event, { isActive }) => { this.isSessionActive = isActive; }; ipcRenderer.on('session-state-changed', this._sessionStateListener); this._shortcutListener = (event, keybinds) => { console.log('[MainHeader] Received updated shortcuts:', keybinds); this.shortcuts = keybinds; }; ipcRenderer.on('shortcuts-updated', this._shortcutListener); } } disconnectedCallback() { super.disconnectedCallback(); this.removeEventListener('animationend', this.handleAnimationEnd); if (this.animationEndTimer) { clearTimeout(this.animationEndTimer); this.animationEndTimer = null; } if (window.require) { const { ipcRenderer } = window.require('electron'); if (this._sessionStateListener) { ipcRenderer.removeListener('session-state-changed', this._sessionStateListener); } if (this._shortcutListener) { ipcRenderer.removeListener('shortcuts-updated', this._shortcutListener); } } } invoke(channel, ...args) { if (this.wasJustDragged) { return; } if (window.require) { window.require('electron').ipcRenderer.invoke(channel, ...args); } } showWindow(name, element) { if (this.wasJustDragged) return; if (window.require) { const { ipcRenderer } = window.require('electron'); console.log(`[MainHeader] showWindow('${name}') called at ${Date.now()}`); ipcRenderer.send('cancel-hide-window', name); if (name === 'settings' && element) { const rect = element.getBoundingClientRect(); ipcRenderer.send('show-window', { name: 'settings', bounds: { x: rect.left, y: rect.top, width: rect.width, height: rect.height } }); } else { ipcRenderer.send('show-window', name); } } } hideWindow(name) { if (this.wasJustDragged) return; if (window.require) { console.log(`[MainHeader] hideWindow('${name}') called at ${Date.now()}`); window.require('electron').ipcRenderer.send('hide-window', name); } } cancelHideWindow(name) { } renderShortcut(accelerator) { if (!accelerator) return html``; const keyMap = { 'Cmd': '⌘', 'Command': '⌘', 'Ctrl': '⌃', 'Control': '⌃', 'Alt': '⌥', 'Option': '⌥', 'Shift': '⇧', 'Enter': '↵', 'Backspace': '⌫', 'Delete': '⌦', 'Tab': '⇥', 'Escape': '⎋', 'Up': '↑', 'Down': '↓', 'Left': '←', 'Right': '→', '\\': html``, }; const keys = accelerator.split('+'); return html`${keys.map(key => html`
${keyMap[key] || key}
`)}`; } render() { return html`
this.invoke('toggle-feature', 'ask')}>
Ask
${this.renderShortcut(this.shortcuts.nextStep)}
this.invoke('toggle-all-windows-visibility')}>
Show/Hide
${this.renderShortcut(this.shortcuts.toggleVisibility)}
`; } } customElements.define('main-header', MainHeader);