- Extract agent badge into AgentBadge component with dropdown (TODO placeholder) - Realtime connection indicated by badge color (green=connected, indigo=disconnected) - Remove Transcript label and chat bubble icon from titlebar - Add force mobile mode button (bottom sheet panel on desktop) - Size toggle persisted in localStorage and controls sheet height in mobile mode - Replace full titlebar drag with thin 5px top edge grip - Remove sheet-handle touch bar, size controlled via toggle button only - Large mobile mode respects app header height - Slide-up/down animation for mobile panel enter/exit
1184 lines
42 KiB
Vue
1184 lines
42 KiB
Vue
<script setup lang="ts">
|
|
import { ref, computed, watch, onMounted, onBeforeUnmount, nextTick } from 'vue'
|
|
import { useTranscriptDebug } from '@/composables/transcript-debug'
|
|
import { ChatContainer, AquaticBackground, AgentBadge } from '@/components/transcript-debug'
|
|
import type { AgentName } from '@/types/transcript-debug'
|
|
|
|
const props = defineProps<{
|
|
modelValue: boolean
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
'update:modelValue': [value: boolean]
|
|
}>()
|
|
|
|
const isOpen = computed({
|
|
get: () => props.modelValue,
|
|
set: (val) => emit('update:modelValue', val)
|
|
})
|
|
|
|
// ============================================================================
|
|
// TRANSCRIPT DEBUG STATE (composable)
|
|
// ============================================================================
|
|
|
|
const {
|
|
selectedAgent,
|
|
sessions,
|
|
selectedSessionId,
|
|
conversation,
|
|
loading,
|
|
error,
|
|
isRealtime,
|
|
processing,
|
|
init,
|
|
switchAgent,
|
|
selectSession,
|
|
disconnectRealtime,
|
|
sendPrompt
|
|
} = useTranscriptDebug()
|
|
|
|
const agents: { id: AgentName; label: string }[] = [
|
|
{ id: 'ejecutor', label: 'Ejecutor' },
|
|
{ id: 'nucleo000', label: 'nucleo000' },
|
|
{ id: 'claude', label: 'Claude' }
|
|
]
|
|
|
|
const showSelector = ref(false)
|
|
const chatRef = ref<InstanceType<typeof ChatContainer> | null>(null)
|
|
let initialized = false
|
|
|
|
// ============================================================================
|
|
// CHROME VISIBILITY (idle mode: hide UI chrome when not interacting)
|
|
// ============================================================================
|
|
|
|
const isHovered = ref(false)
|
|
const isFocusWithin = ref(false)
|
|
|
|
const showChrome = computed(() =>
|
|
isHovered.value || isFocusWithin.value || isDragging.value ||
|
|
isResizing.value || showSelector.value
|
|
)
|
|
|
|
// ============================================================================
|
|
// DRAG STATE
|
|
// ============================================================================
|
|
|
|
const windowRef = ref<HTMLElement | null>(null)
|
|
const isDragging = ref(false)
|
|
const position = ref({ x: 0, y: 0 })
|
|
const hasCustomPosition = ref(false)
|
|
const dragOffset = ref({ x: 0, y: 0 })
|
|
|
|
// Resize state
|
|
const isResizing = ref(false)
|
|
const size = ref({ w: 480, h: 600 })
|
|
|
|
// Zoom level for content scaling
|
|
const zoom = ref(1)
|
|
|
|
// Size mode: pin (small, anchored to FAB), medium (default), large
|
|
type SizeMode = 'pin' | 'medium' | 'large'
|
|
const savedSize = localStorage.getItem('transcript-size-mode') as SizeMode | null
|
|
const sizeMode = ref<SizeMode>(savedSize && ['pin', 'medium', 'large'].includes(savedSize) ? savedSize : 'medium')
|
|
|
|
// Force mobile (bottom sheet) mode on desktop
|
|
const forceMobile = ref(false)
|
|
const effectiveMobile = computed(() => isMobile.value || forceMobile.value)
|
|
|
|
function toggleForceMobile() {
|
|
if (isMobile.value) return // already native mobile
|
|
forceMobile.value = !forceMobile.value
|
|
if (forceMobile.value) {
|
|
// Enter mobile: set sheet height based on current sizeMode
|
|
const mobileSnaps: Record<SizeMode, number> = { pin: 20, medium: 55, large: 100 }
|
|
sheetHeight.value = mobileSnaps[sizeMode.value]
|
|
hasCustomPosition.value = false
|
|
}
|
|
}
|
|
|
|
function cycleSizeMode() {
|
|
const modes: SizeMode[] = ['pin', 'medium', 'large']
|
|
const i = modes.indexOf(sizeMode.value)
|
|
sizeMode.value = modes[(i + 1) % modes.length]
|
|
localStorage.setItem('transcript-size-mode', sizeMode.value)
|
|
hasCustomPosition.value = false
|
|
|
|
// In mobile mode, also update sheet height
|
|
if (effectiveMobile.value) {
|
|
const mobileSnaps: Record<SizeMode, number> = { pin: 20, medium: 55, large: 100 }
|
|
sheetHeight.value = mobileSnaps[sizeMode.value]
|
|
}
|
|
}
|
|
|
|
// Mobile bottom sheet state
|
|
const isMobile = ref(false)
|
|
const sheetHeight = ref(55)
|
|
|
|
// ============================================================================
|
|
// MOBILE DETECTION
|
|
// ============================================================================
|
|
|
|
function checkMobile() {
|
|
const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0
|
|
const isSmallScreen = window.innerWidth <= 1024
|
|
isMobile.value = isTouchDevice && isSmallScreen
|
|
}
|
|
|
|
|
|
// ============================================================================
|
|
// WINDOW DRAG
|
|
// ============================================================================
|
|
|
|
function startDrag(e: MouseEvent) {
|
|
if (effectiveMobile.value) return
|
|
|
|
isDragging.value = true
|
|
const rect = windowRef.value?.getBoundingClientRect()
|
|
if (rect) {
|
|
if (!hasCustomPosition.value) {
|
|
position.value = { x: rect.left, y: rect.top }
|
|
}
|
|
dragOffset.value = { x: e.clientX - rect.left, y: e.clientY - rect.top }
|
|
}
|
|
|
|
document.addEventListener('mousemove', onDrag)
|
|
document.addEventListener('mouseup', stopDrag)
|
|
}
|
|
|
|
function onDrag(e: MouseEvent) {
|
|
if (!isDragging.value) return
|
|
|
|
const w = windowRef.value?.offsetWidth || 480
|
|
const h = windowRef.value?.offsetHeight || 600
|
|
const minX = -w * 0.75
|
|
const maxX = window.innerWidth - w * 0.25
|
|
const minY = -h * 0.75
|
|
const maxY = window.innerHeight - h * 0.25
|
|
|
|
position.value = {
|
|
x: Math.max(minX, Math.min(e.clientX - dragOffset.value.x, maxX)),
|
|
y: Math.max(minY, Math.min(e.clientY - dragOffset.value.y, maxY))
|
|
}
|
|
}
|
|
|
|
function stopDrag() {
|
|
isDragging.value = false
|
|
hasCustomPosition.value = true
|
|
document.removeEventListener('mousemove', onDrag)
|
|
document.removeEventListener('mouseup', stopDrag)
|
|
}
|
|
|
|
// ============================================================================
|
|
// WINDOW RESIZE
|
|
// ============================================================================
|
|
|
|
const resizeStart = ref({ x: 0, y: 0, w: 0, h: 0 })
|
|
|
|
function startResize(e: MouseEvent) {
|
|
e.preventDefault()
|
|
e.stopPropagation()
|
|
isResizing.value = true
|
|
resizeStart.value = { x: e.clientX, y: e.clientY, w: size.value.w, h: size.value.h }
|
|
document.addEventListener('mousemove', onResize)
|
|
document.addEventListener('mouseup', stopResize)
|
|
}
|
|
|
|
function onResize(e: MouseEvent) {
|
|
if (!isResizing.value) return
|
|
size.value = {
|
|
w: Math.max(360, Math.min(resizeStart.value.w + e.clientX - resizeStart.value.x, window.innerWidth - 40)),
|
|
h: Math.max(300, Math.min(resizeStart.value.h + e.clientY - resizeStart.value.y, window.innerHeight - 40))
|
|
}
|
|
}
|
|
|
|
function stopResize() {
|
|
isResizing.value = false
|
|
document.removeEventListener('mousemove', onResize)
|
|
document.removeEventListener('mouseup', stopResize)
|
|
}
|
|
|
|
// ============================================================================
|
|
// COMPUTED STYLE
|
|
// ============================================================================
|
|
|
|
const sizeModePresets: Record<SizeMode, { w: number; h: number }> = {
|
|
pin: { w: 240, h: 300 },
|
|
medium: { w: 480, h: 600 },
|
|
large: { w: 800, h: 760 }
|
|
}
|
|
|
|
const windowStyle = computed((): Record<string, string> => {
|
|
if (effectiveMobile.value) {
|
|
const isFullScreen = sheetHeight.value >= 100
|
|
const style: Record<string, string> = {
|
|
position: 'fixed',
|
|
left: '0',
|
|
right: '0',
|
|
bottom: '0',
|
|
width: '100%',
|
|
}
|
|
if (isFullScreen) {
|
|
// Full height but respect app header
|
|
const headerEl = document.querySelector('.app-header') as HTMLElement | null
|
|
const headerH = headerEl ? headerEl.offsetHeight : 40
|
|
style.top = `${headerH}px`
|
|
style.height = 'auto'
|
|
} else {
|
|
style.height = `${sheetHeight.value}dvh`
|
|
}
|
|
return style
|
|
}
|
|
|
|
const preset = sizeModePresets[sizeMode.value]
|
|
|
|
// Custom position from dragging (uses current resize size or preset)
|
|
if (hasCustomPosition.value) {
|
|
const w = sizeMode.value === 'medium' ? size.value.w : preset.w
|
|
const h = sizeMode.value === 'medium' ? size.value.h : preset.h
|
|
return {
|
|
width: `${w}px`,
|
|
height: `${h}px`,
|
|
top: `${position.value.y}px`,
|
|
left: `${position.value.x}px`,
|
|
bottom: 'auto',
|
|
right: 'auto'
|
|
}
|
|
}
|
|
|
|
// Pin: anchored to FAB button (bottom-left corner aligned)
|
|
if (sizeMode.value === 'pin') {
|
|
return {
|
|
width: `${preset.w}px`,
|
|
height: `${preset.h}px`,
|
|
bottom: '20px',
|
|
left: '80px'
|
|
}
|
|
}
|
|
|
|
// Large: centered-ish, generous size
|
|
if (sizeMode.value === 'large') {
|
|
return {
|
|
width: `${preset.w}px`,
|
|
height: `${preset.h}px`,
|
|
bottom: '16px',
|
|
left: '90px'
|
|
}
|
|
}
|
|
|
|
// Medium (default)
|
|
return {
|
|
width: `${size.value.w}px`,
|
|
height: `${size.value.h}px`,
|
|
bottom: '16px',
|
|
left: '90px'
|
|
}
|
|
})
|
|
|
|
// ============================================================================
|
|
// ACTIONS
|
|
// ============================================================================
|
|
|
|
function close() {
|
|
isOpen.value = false
|
|
showSelector.value = false
|
|
}
|
|
|
|
function openAtCursor(x: number, y: number) {
|
|
if (effectiveMobile.value) {
|
|
isOpen.value = !isOpen.value
|
|
return
|
|
}
|
|
if (isOpen.value) {
|
|
isOpen.value = false
|
|
return
|
|
}
|
|
const preset = sizeModePresets[sizeMode.value]
|
|
const w = sizeMode.value === 'medium' ? size.value.w : preset.w
|
|
const h = sizeMode.value === 'medium' ? size.value.h : preset.h
|
|
const pad = 8
|
|
const vw = window.innerWidth
|
|
const vh = window.innerHeight
|
|
// Respect app header bar (~40px + safe-area)
|
|
const headerEl = document.querySelector('.app-header') as HTMLElement | null
|
|
const topBarrier = headerEl ? headerEl.getBoundingClientRect().bottom + pad : pad + 40
|
|
|
|
// Cursor at bottom-center of window: window extends upward from cursor
|
|
let left = x - w / 2
|
|
let top = y - h
|
|
|
|
// Clamp horizontally
|
|
left = Math.max(pad, Math.min(left, vw - w - pad))
|
|
|
|
// Clamp vertically: never above header, never below viewport
|
|
top = Math.max(topBarrier, Math.min(top, vh - h - pad))
|
|
|
|
position.value = { x: left, y: top }
|
|
hasCustomPosition.value = true
|
|
isOpen.value = true
|
|
nextTick(() => {
|
|
windowRef.value?.querySelector<HTMLTextAreaElement>('.input-field')?.focus()
|
|
})
|
|
}
|
|
|
|
defineExpose({ openAtCursor })
|
|
|
|
function handleAgentSwitch(agent: AgentName) {
|
|
switchAgent(agent)
|
|
}
|
|
|
|
function handleSessionSelect(sessionId: string) {
|
|
selectSession(sessionId)
|
|
showSelector.value = false
|
|
}
|
|
|
|
function handleSend(message: string) {
|
|
sendPrompt(message)
|
|
}
|
|
|
|
// ============================================================================
|
|
// WATCHERS
|
|
// ============================================================================
|
|
|
|
watch(isOpen, async (open) => {
|
|
if (open && !initialized) {
|
|
initialized = true
|
|
await nextTick()
|
|
init()
|
|
}
|
|
})
|
|
|
|
// ============================================================================
|
|
// ZOOM KEYBOARD HANDLER
|
|
// ============================================================================
|
|
|
|
function handleZoomKey(e: KeyboardEvent) {
|
|
if (!isOpen.value || !e.ctrlKey) return
|
|
if (e.key === '+' || e.key === '=') {
|
|
e.preventDefault()
|
|
zoom.value = Math.min(2, +(zoom.value + 0.1).toFixed(1))
|
|
} else if (e.key === '-') {
|
|
e.preventDefault()
|
|
zoom.value = Math.max(0.5, +(zoom.value - 0.1).toFixed(1))
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// LIFECYCLE
|
|
// ============================================================================
|
|
|
|
onMounted(() => {
|
|
checkMobile()
|
|
window.addEventListener('resize', checkMobile)
|
|
document.addEventListener('keydown', handleZoomKey)
|
|
})
|
|
|
|
onBeforeUnmount(() => {
|
|
disconnectRealtime()
|
|
document.removeEventListener('keydown', handleZoomKey)
|
|
document.removeEventListener('mousemove', onDrag)
|
|
document.removeEventListener('mouseup', stopDrag)
|
|
document.removeEventListener('mousemove', onResize)
|
|
document.removeEventListener('mouseup', stopResize)
|
|
window.removeEventListener('resize', checkMobile)
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<Teleport to="body">
|
|
<Transition name="win-slide">
|
|
<div
|
|
v-show="isOpen"
|
|
ref="windowRef"
|
|
class="aero-win"
|
|
:class="{
|
|
dragging: isDragging,
|
|
resizing: isResizing,
|
|
mobile: effectiveMobile,
|
|
'chrome-visible': showChrome,
|
|
'selector-open': showSelector
|
|
}"
|
|
:style="windowStyle"
|
|
@mouseenter="isHovered = true"
|
|
@mouseleave="isHovered = false"
|
|
@focusin="isFocusWithin = true"
|
|
@focusout="isFocusWithin = false"
|
|
>
|
|
<div class="glass" :style="{ zoom: zoom !== 1 ? zoom : undefined }">
|
|
<!-- Thin drag edge (replaces full titlebar drag) -->
|
|
<div
|
|
v-if="!effectiveMobile"
|
|
class="drag-edge"
|
|
@mousedown="startDrag"
|
|
></div>
|
|
|
|
<!-- Titlebar -->
|
|
<div class="titlebar">
|
|
<div class="left">
|
|
<AgentBadge v-if="selectedAgent" :agent="selectedAgent" :connected="isRealtime" />
|
|
</div>
|
|
<div class="window-controls">
|
|
<button
|
|
v-if="!isMobile"
|
|
@click.stop="toggleForceMobile"
|
|
class="mobile-btn"
|
|
:class="{ active: forceMobile }"
|
|
title="Toggle mobile panel"
|
|
>
|
|
<!-- Phone/panel-from-bottom icon -->
|
|
<svg width="10" height="10" viewBox="0 0 10 10" shape-rendering="crispEdges">
|
|
<rect x="2" y="0" width="6" height="10" fill="currentColor" opacity="0.25"/>
|
|
<rect x="3" y="1" width="4" height="7" fill="currentColor"/>
|
|
<rect x="4" y="9" width="2" height="1" fill="currentColor" opacity="0.5"/>
|
|
</svg>
|
|
</button>
|
|
<button @click.stop="cycleSizeMode" class="size-btn" :title="`Size: ${sizeMode}`">
|
|
<!-- Pin icon: small square bottom-left -->
|
|
<svg v-if="sizeMode === 'pin'" width="10" height="10" viewBox="0 0 10 10" shape-rendering="crispEdges">
|
|
<rect x="0" y="6" width="4" height="4" fill="currentColor"/>
|
|
</svg>
|
|
<!-- Medium icon: medium square centered -->
|
|
<svg v-else-if="sizeMode === 'medium'" width="10" height="10" viewBox="0 0 10 10" shape-rendering="crispEdges">
|
|
<rect x="2" y="2" width="6" height="6" fill="currentColor" opacity="0.35"/>
|
|
<rect x="3" y="3" width="4" height="4" fill="currentColor"/>
|
|
</svg>
|
|
<!-- Large icon: full square -->
|
|
<svg v-else width="10" height="10" viewBox="0 0 10 10" shape-rendering="crispEdges">
|
|
<rect x="0" y="0" width="10" height="10" fill="currentColor" opacity="0.35"/>
|
|
<rect x="1" y="1" width="8" height="8" fill="currentColor"/>
|
|
</svg>
|
|
</button>
|
|
<button @click.stop="chatRef?.toggleSelectMode()" :class="{ active: chatRef?.selectMode }" title="Select messages">
|
|
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<polyline v-if="chatRef?.selectMode" points="20 6 9 17 4 12"/>
|
|
<template v-else>
|
|
<rect x="3" y="3" width="7" height="7" rx="1"/>
|
|
<rect x="14" y="3" width="7" height="7" rx="1"/>
|
|
<rect x="3" y="14" width="7" height="7" rx="1"/>
|
|
<rect x="14" y="14" width="7" height="7" rx="1"/>
|
|
</template>
|
|
</svg>
|
|
</button>
|
|
<button @click.stop="showSelector = !showSelector" :class="{ active: showSelector }" title="Agent/Session">
|
|
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<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 0 1 0 2.83 2 2 0 0 1-2.83 0l-.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-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 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 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>
|
|
</svg>
|
|
</button>
|
|
<button class="x" @click="close" title="Close">
|
|
<svg width="8" height="8" viewBox="0 0 10 10">
|
|
<line x1="0" y1="0" x2="10" y2="10" stroke="currentColor" stroke-width="1.5"/>
|
|
<line x1="10" y1="0" x2="0" y2="10" stroke="currentColor" stroke-width="1.5"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Error -->
|
|
<div v-if="error" class="error-bar">{{ error }}</div>
|
|
|
|
<!-- Content -->
|
|
<div class="content">
|
|
<AquaticBackground />
|
|
<div class="readability-overlay" />
|
|
<ChatContainer
|
|
ref="chatRef"
|
|
v-if="conversation"
|
|
:conversation="conversation"
|
|
:processing="processing"
|
|
:show-selector="showSelector"
|
|
:agents="agents"
|
|
:selected-agent="selectedAgent"
|
|
:sessions="sessions"
|
|
:selected-session-id="selectedSessionId"
|
|
:sessions-loading="loading"
|
|
@send="handleSend"
|
|
@switch-agent="handleAgentSwitch"
|
|
@select-session="handleSessionSelect"
|
|
/>
|
|
<div v-else class="empty-state">
|
|
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
|
|
</svg>
|
|
<span>Select a session to begin</span>
|
|
<small>{{ sessions.length }} sessions available</small>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Resize handle -->
|
|
<div v-if="!effectiveMobile" class="resize-handle" @mousedown="startResize"></div>
|
|
</div>
|
|
</div>
|
|
</Transition>
|
|
</Teleport>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.aero-win {
|
|
position: fixed;
|
|
min-width: 200px;
|
|
min-height: 200px;
|
|
z-index: 9999;
|
|
transition: width 0.3s ease, height 0.3s ease, top 0.3s ease, left 0.3s ease, bottom 0.3s ease;
|
|
}
|
|
|
|
/* ── Dynamic glow from ocean background ── */
|
|
.aero-win::before {
|
|
content: '';
|
|
position: absolute;
|
|
inset: -12px;
|
|
z-index: -1;
|
|
border-radius: 18px;
|
|
background: linear-gradient(
|
|
180deg,
|
|
rgba(0, 12, 35, 0.35) 0%,
|
|
rgba(0, 30, 65, 0.30) 25%,
|
|
rgba(4, 52, 78, 0.35) 50%,
|
|
rgba(6, 58, 72, 0.30) 70%,
|
|
rgba(18, 50, 45, 0.35) 100%
|
|
);
|
|
filter: blur(18px);
|
|
opacity: 0.7;
|
|
transition: opacity 0.5s ease, filter 0.5s ease;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.aero-win:hover::before,
|
|
.aero-win.chrome-visible::before {
|
|
opacity: 1;
|
|
filter: blur(22px);
|
|
}
|
|
|
|
.aero-win.dragging::before,
|
|
.aero-win.resizing::before {
|
|
transition: none;
|
|
}
|
|
|
|
.aero-win.dragging,
|
|
.aero-win.resizing {
|
|
transition: none;
|
|
}
|
|
|
|
.glass {
|
|
position: relative;
|
|
width: 100%;
|
|
height: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
background: rgba(0, 0, 0, 0.78);
|
|
backdrop-filter: blur(16px) saturate(1.2);
|
|
-webkit-backdrop-filter: blur(16px) saturate(1.2);
|
|
border-radius: 10px;
|
|
border: 1px solid rgba(255,255,255,0.05);
|
|
box-shadow:
|
|
0 0 0 1px rgba(0,0,0,0.6),
|
|
0 16px 56px rgba(0,0,0,0.6),
|
|
0 4px 16px rgba(0,0,0,0.4);
|
|
overflow: hidden;
|
|
transition: border-color 0.35s ease, box-shadow 0.35s ease;
|
|
}
|
|
|
|
/* ══════════════════════════════════════════════════════════════════════════
|
|
IDLE MODE: When not hovering/focused, hide all chrome.
|
|
Only the messages + ocean background fill the entire window.
|
|
══════════════════════════════════════════════════════════════════════════ */
|
|
|
|
/* Transition all chrome elements smoothly */
|
|
.titlebar,
|
|
.error-bar,
|
|
.resize-handle {
|
|
transition: opacity 0.35s ease, max-height 0.35s ease, padding 0.35s ease;
|
|
}
|
|
|
|
/* Idle: hide drag edge */
|
|
.aero-win:not(.chrome-visible) .drag-edge {
|
|
pointer-events: none;
|
|
}
|
|
|
|
/* Idle: slide titlebar up and fade out */
|
|
.aero-win:not(.chrome-visible) .titlebar {
|
|
opacity: 0;
|
|
transform: translateY(-100%);
|
|
pointer-events: none;
|
|
}
|
|
|
|
/* Idle: hide error bar */
|
|
.aero-win:not(.chrome-visible) .error-bar {
|
|
opacity: 0;
|
|
max-height: 0;
|
|
padding: 0;
|
|
border-bottom-width: 0;
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* Idle: hide resize handle */
|
|
.aero-win:not(.chrome-visible) .resize-handle {
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
}
|
|
|
|
/* Chat-header: only visible when settings/selector is open */
|
|
.aero-win:not(.selector-open) .content :deep(.chat-header) {
|
|
opacity: 0 !important;
|
|
transform: translateY(-150%) !important;
|
|
pointer-events: none !important;
|
|
}
|
|
|
|
/* Idle: slide user-input down and fade out (only if empty) */
|
|
.aero-win:not(.chrome-visible) .content :deep(.user-input) {
|
|
opacity: 0 !important;
|
|
transform: translateY(100%) !important;
|
|
pointer-events: none !important;
|
|
}
|
|
|
|
/* Keep user-input visible when textarea has text */
|
|
.aero-win:not(.chrome-visible) .content :deep(.user-input:has(.input-field:not(:placeholder-shown))) {
|
|
opacity: 1 !important;
|
|
transform: none !important;
|
|
pointer-events: auto !important;
|
|
}
|
|
|
|
/* Idle: hide status bar */
|
|
.aero-win:not(.chrome-visible) .content :deep(.status-bar) {
|
|
opacity: 0 !important;
|
|
transform: translateY(100%) !important;
|
|
pointer-events: none !important;
|
|
}
|
|
|
|
/* Keep status-bar visible when input has text */
|
|
.aero-win:not(.chrome-visible) .content :deep(.status-bar:has(~ .user-input .input-field:not(:placeholder-shown))) {
|
|
opacity: 1 !important;
|
|
transform: none !important;
|
|
pointer-events: auto !important;
|
|
}
|
|
|
|
/* Idle: also hide selection bar */
|
|
.aero-win:not(.chrome-visible) .content :deep(.selection-bar) {
|
|
opacity: 0 !important;
|
|
pointer-events: none !important;
|
|
}
|
|
|
|
/* Idle: softer glass border + dimmed glow */
|
|
.aero-win:not(.chrome-visible) .glass {
|
|
border-color: rgba(255,255,255,0.02);
|
|
box-shadow:
|
|
0 0 0 1px rgba(0,0,0,0.3),
|
|
0 8px 32px rgba(0,0,0,0.4);
|
|
}
|
|
|
|
.aero-win:not(.chrome-visible)::before {
|
|
opacity: 0.4;
|
|
filter: blur(14px);
|
|
}
|
|
|
|
/* Smooth transitions for chrome show/hide */
|
|
.content :deep(.chat-header) {
|
|
transition: opacity 0.35s ease, transform 0.35s ease !important;
|
|
}
|
|
|
|
.content :deep(.user-input) {
|
|
transition: opacity 0.35s ease, transform 0.35s ease !important;
|
|
}
|
|
|
|
.content :deep(.status-bar) {
|
|
transition: opacity 0.35s ease, transform 0.35s ease !important;
|
|
}
|
|
|
|
/* ── Drag edge: thin strip at top for window dragging ── */
|
|
.drag-edge {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 5px;
|
|
z-index: 11;
|
|
cursor: grab;
|
|
}
|
|
|
|
.drag-edge:active {
|
|
cursor: grabbing;
|
|
}
|
|
|
|
.aero-win.dragging .drag-edge {
|
|
cursor: grabbing;
|
|
}
|
|
|
|
/* ── Titlebar: absolute overlay at top of .glass ── */
|
|
.titlebar {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
z-index: 10;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
height: 28px;
|
|
padding: 0 5px 0 8px;
|
|
background:
|
|
/* Pixel art sea surface: sky, moon, waves, fish, bubbles */
|
|
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='28' viewBox='0 0 120 28' shape-rendering='crispEdges'%3E%3Crect x='0' y='0' width='120' height='12' fill='%230a1628' opacity='0.5'/%3E%3Crect x='0' y='12' width='120' height='16' fill='%230c2d4a' opacity='0.45'/%3E%3Crect x='8' y='3' width='1' height='1' fill='white' opacity='0.3'/%3E%3Crect x='25' y='5' width='1' height='1' fill='white' opacity='0.22'/%3E%3Crect x='45' y='2' width='1' height='1' fill='white' opacity='0.18'/%3E%3Crect x='70' y='4' width='1' height='1' fill='white' opacity='0.28'/%3E%3Crect x='95' y='6' width='1' height='1' fill='white' opacity='0.22'/%3E%3Crect x='110' y='3' width='1' height='1' fill='white' opacity='0.18'/%3E%3Crect x='36' y='7' width='1' height='1' fill='%23fde68a' opacity='0.15'/%3E%3Crect x='85' y='2' width='3' height='3' fill='%23fef3c7' opacity='0.3'/%3E%3Crect x='86' y='1' width='2' height='1' fill='%23fef3c7' opacity='0.2'/%3E%3Crect x='86' y='5' width='2' height='1' fill='%23fde68a' opacity='0.15'/%3E%3Crect x='50' y='4' width='4' height='2' fill='%23475569' opacity='0.15'/%3E%3Crect x='51' y='3' width='2' height='1' fill='%23475569' opacity='0.1'/%3E%3Crect x='8' y='10' width='4' height='2' fill='%2322d3ee' opacity='0.3'/%3E%3Crect x='12' y='11' width='10' height='2' fill='%230ea5e9' opacity='0.25'/%3E%3Crect x='22' y='10' width='6' height='2' fill='%2322d3ee' opacity='0.3'/%3E%3Crect x='28' y='11' width='12' height='2' fill='%230ea5e9' opacity='0.25'/%3E%3Crect x='40' y='10' width='4' height='2' fill='%2322d3ee' opacity='0.3'/%3E%3Crect x='44' y='11' width='14' height='2' fill='%230ea5e9' opacity='0.25'/%3E%3Crect x='58' y='10' width='6' height='2' fill='%2322d3ee' opacity='0.3'/%3E%3Crect x='64' y='11' width='10' height='2' fill='%230ea5e9' opacity='0.25'/%3E%3Crect x='74' y='10' width='4' height='2' fill='%2322d3ee' opacity='0.3'/%3E%3Crect x='78' y='11' width='12' height='2' fill='%230ea5e9' opacity='0.25'/%3E%3Crect x='90' y='10' width='6' height='2' fill='%2322d3ee' opacity='0.3'/%3E%3Crect x='96' y='11' width='14' height='2' fill='%230ea5e9' opacity='0.25'/%3E%3Crect x='110' y='10' width='4' height='2' fill='%2322d3ee' opacity='0.3'/%3E%3Crect x='114' y='11' width='6' height='2' fill='%230ea5e9' opacity='0.25'/%3E%3Crect x='0' y='11' width='8' height='2' fill='%230ea5e9' opacity='0.25'/%3E%3Crect x='8' y='10' width='2' height='1' fill='white' opacity='0.12'/%3E%3Crect x='22' y='10' width='2' height='1' fill='white' opacity='0.1'/%3E%3Crect x='40' y='10' width='2' height='1' fill='white' opacity='0.12'/%3E%3Crect x='58' y='10' width='2' height='1' fill='white' opacity='0.1'/%3E%3Crect x='74' y='10' width='2' height='1' fill='white' opacity='0.12'/%3E%3Crect x='90' y='10' width='2' height='1' fill='white' opacity='0.1'/%3E%3Crect x='110' y='10' width='2' height='1' fill='white' opacity='0.12'/%3E%3Crect x='0' y='14' width='120' height='2' fill='%230369a1' opacity='0.18'/%3E%3Crect x='0' y='18' width='120' height='2' fill='%23075985' opacity='0.12'/%3E%3Crect x='0' y='22' width='120' height='2' fill='%230c4a6e' opacity='0.08'/%3E%3Crect x='15' y='16' width='3' height='1' fill='%23f97316' opacity='0.3'/%3E%3Crect x='14' y='16' width='1' height='1' fill='%23fb923c' opacity='0.2'/%3E%3Crect x='55' y='20' width='3' height='1' fill='%23818cf8' opacity='0.22'/%3E%3Crect x='58' y='20' width='1' height='1' fill='%23a78bfa' opacity='0.15'/%3E%3Crect x='100' y='17' width='3' height='1' fill='%2322c55e' opacity='0.22'/%3E%3Crect x='99' y='17' width='1' height='1' fill='%234ade80' opacity='0.15'/%3E%3Crect x='30' y='15' width='1' height='1' fill='white' opacity='0.1'/%3E%3Crect x='65' y='22' width='1' height='1' fill='white' opacity='0.08'/%3E%3Crect x='82' y='14' width='1' height='1' fill='white' opacity='0.1'/%3E%3C/svg%3E") repeat-x left top / auto 100%,
|
|
rgba(0, 8, 20, 0.3);
|
|
backdrop-filter: blur(8px);
|
|
-webkit-backdrop-filter: blur(8px);
|
|
border-bottom: 1px solid rgba(255,255,255,0.04);
|
|
user-select: none;
|
|
transition: opacity 0.35s ease, transform 0.35s ease;
|
|
}
|
|
|
|
.left {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
color: rgba(255,255,255,0.6);
|
|
font: 500 10px/1 'Courier New', monospace;
|
|
}
|
|
|
|
.window-controls {
|
|
display: flex;
|
|
gap: 2px;
|
|
}
|
|
|
|
.window-controls button {
|
|
width: 20px;
|
|
height: 18px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: rgba(255,255,255,0.04);
|
|
border: 1px solid rgba(255,255,255,0.06);
|
|
border-radius: 0;
|
|
color: rgba(255,255,255,0.4);
|
|
cursor: pointer;
|
|
transition: all 0.15s;
|
|
}
|
|
|
|
.window-controls button:hover {
|
|
background: rgba(255,255,255,0.1);
|
|
color: rgba(255,255,255,0.8);
|
|
}
|
|
|
|
.window-controls button.active {
|
|
background: rgba(99, 102, 241, 0.2);
|
|
border-color: rgba(99, 102, 241, 0.3);
|
|
color: #a5b4fc;
|
|
}
|
|
|
|
.window-controls .mobile-btn {
|
|
color: #a78bfa;
|
|
}
|
|
|
|
.window-controls .mobile-btn:hover {
|
|
color: #c4b5fd;
|
|
background: rgba(167, 139, 250, 0.15);
|
|
border-color: rgba(167, 139, 250, 0.25);
|
|
}
|
|
|
|
.window-controls .mobile-btn.active {
|
|
background: rgba(167, 139, 250, 0.2);
|
|
border-color: rgba(167, 139, 250, 0.3);
|
|
color: #c4b5fd;
|
|
}
|
|
|
|
.window-controls .size-btn {
|
|
color: #0ea5e9;
|
|
}
|
|
|
|
.window-controls .size-btn:hover {
|
|
color: #38bdf8;
|
|
background: rgba(14, 165, 233, 0.15);
|
|
border-color: rgba(14, 165, 233, 0.25);
|
|
}
|
|
|
|
.window-controls button.x:hover {
|
|
background: rgba(239, 68, 68, 0.3);
|
|
border-color: rgba(239, 68, 68, 0.4);
|
|
color: #fca5a5;
|
|
}
|
|
|
|
/* ── Error Bar ── */
|
|
.error-bar {
|
|
padding: 4px 10px;
|
|
background: rgba(239, 68, 68, 0.1);
|
|
border-bottom: 1px solid rgba(239, 68, 68, 0.2);
|
|
color: #fca5a5;
|
|
font-size: 10px;
|
|
font-family: 'Courier New', monospace;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
/* ── Content ── */
|
|
.content {
|
|
flex: 1;
|
|
overflow: hidden;
|
|
display: flex;
|
|
flex-direction: column;
|
|
position: relative;
|
|
/* Background handled by AquaticBackground component */
|
|
}
|
|
|
|
/* Dark readability overlay: between ocean bg (z-index:0) and chat (z-index:1) */
|
|
.readability-overlay {
|
|
position: absolute;
|
|
inset: 0;
|
|
z-index: 0;
|
|
background: rgba(0, 0, 0, 0.55);
|
|
pointer-events: none;
|
|
opacity: 0;
|
|
transition: opacity 0.35s ease;
|
|
}
|
|
|
|
.aero-win.chrome-visible .readability-overlay {
|
|
opacity: 1;
|
|
}
|
|
|
|
/* Override ChatContainer backgrounds: glass-transparent */
|
|
.content :deep(.chat-container) {
|
|
background: transparent !important;
|
|
border: none !important;
|
|
border-radius: 0 !important;
|
|
position: relative;
|
|
z-index: 1;
|
|
flex: 1 !important;
|
|
min-height: 0 !important;
|
|
}
|
|
|
|
/* Chat header: absolute overlay below titlebar, floats over messages */
|
|
.content :deep(.chat-header) {
|
|
position: absolute !important;
|
|
top: 28px !important;
|
|
left: 0 !important;
|
|
right: 0 !important;
|
|
z-index: 3 !important;
|
|
background: rgba(0, 6, 18, 0.5) !important;
|
|
backdrop-filter: blur(8px) !important;
|
|
-webkit-backdrop-filter: blur(8px) !important;
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.06) !important;
|
|
padding: 0.3rem 0.6rem !important;
|
|
}
|
|
|
|
/* Messages: fill entire container, pad top/bottom for overlaid titlebar+header / input */
|
|
.content :deep(.messages-scroll) {
|
|
background: transparent !important;
|
|
padding-top: 5rem !important;
|
|
padding-bottom: 5rem !important;
|
|
flex: 1 !important;
|
|
scrollbar-gutter: stable !important;
|
|
}
|
|
|
|
.content :deep(.meta-badge) {
|
|
background: rgba(255,255,255,0.04);
|
|
color: rgba(255,255,255,0.4);
|
|
border-radius: 0;
|
|
font-family: 'Courier New', monospace;
|
|
}
|
|
|
|
.content :deep(.meta-badge.model) {
|
|
background: rgba(99, 102, 241, 0.1);
|
|
color: #a5b4fc;
|
|
}
|
|
|
|
.content :deep(.meta-cwd),
|
|
.content :deep(.meta-duration),
|
|
.content :deep(.meta-count) {
|
|
color: rgba(255,255,255,0.25);
|
|
}
|
|
|
|
.content :deep(.copy-id-btn) {
|
|
color: rgba(255,255,255,0.25);
|
|
}
|
|
|
|
.content :deep(.copy-id-btn:hover) {
|
|
background: rgba(255,255,255,0.06);
|
|
color: rgba(255,255,255,0.6);
|
|
}
|
|
|
|
.content :deep(.select-mode-btn) {
|
|
border-color: rgba(255,255,255,0.08);
|
|
color: rgba(255,255,255,0.35);
|
|
border-radius: 0;
|
|
}
|
|
|
|
.content :deep(.select-mode-btn:hover) {
|
|
background: rgba(255,255,255,0.06);
|
|
color: rgba(255,255,255,0.7);
|
|
}
|
|
|
|
.content :deep(.select-mode-btn.active) {
|
|
background: rgba(99, 102, 241, 0.25);
|
|
border-color: rgba(99, 102, 241, 0.35);
|
|
color: #c7d2fe;
|
|
}
|
|
|
|
/* Pixel art scrollbar */
|
|
.content :deep(.messages-scroll)::-webkit-scrollbar {
|
|
width: 8px;
|
|
transition: opacity 0.35s ease;
|
|
}
|
|
|
|
/* Idle: hide scrollbar visuals (gutter stays via scrollbar-gutter: stable) */
|
|
.aero-win:not(.chrome-visible) .content :deep(.messages-scroll)::-webkit-scrollbar-track {
|
|
background: transparent !important;
|
|
}
|
|
|
|
.aero-win:not(.chrome-visible) .content :deep(.messages-scroll)::-webkit-scrollbar-thumb {
|
|
background: transparent !important;
|
|
border-color: transparent !important;
|
|
}
|
|
|
|
.content :deep(.messages-scroll)::-webkit-scrollbar-track {
|
|
background:
|
|
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8' shape-rendering='crispEdges'%3E%3Crect x='0' y='0' width='8' height='8' fill='%230c2d4a' opacity='0.4'/%3E%3Crect x='2' y='2' width='2' height='2' fill='%23075985' opacity='0.15'/%3E%3Crect x='6' y='6' width='2' height='2' fill='%23075985' opacity='0.1'/%3E%3C/svg%3E") repeat;
|
|
}
|
|
|
|
.content :deep(.messages-scroll)::-webkit-scrollbar-thumb {
|
|
background:
|
|
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='16' viewBox='0 0 8 16' shape-rendering='crispEdges'%3E%3Crect x='0' y='0' width='8' height='16' fill='%230ea5e9' opacity='0.3'/%3E%3Crect x='2' y='2' width='4' height='2' fill='%2322d3ee' opacity='0.25'/%3E%3Crect x='2' y='6' width='4' height='2' fill='%2367e8f9' opacity='0.2'/%3E%3Crect x='2' y='10' width='4' height='2' fill='%2322d3ee' opacity='0.25'/%3E%3Crect x='2' y='14' width='4' height='2' fill='%2367e8f9' opacity='0.15'/%3E%3C/svg%3E") repeat;
|
|
border: 1px solid rgba(14, 165, 233, 0.15);
|
|
}
|
|
|
|
.content :deep(.messages-scroll)::-webkit-scrollbar-thumb:hover {
|
|
background:
|
|
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='16' viewBox='0 0 8 16' shape-rendering='crispEdges'%3E%3Crect x='0' y='0' width='8' height='16' fill='%230ea5e9' opacity='0.45'/%3E%3Crect x='2' y='2' width='4' height='2' fill='%2322d3ee' opacity='0.35'/%3E%3Crect x='2' y='6' width='4' height='2' fill='%2367e8f9' opacity='0.3'/%3E%3Crect x='2' y='10' width='4' height='2' fill='%2322d3ee' opacity='0.35'/%3E%3Crect x='2' y='14' width='4' height='2' fill='%2367e8f9' opacity='0.25'/%3E%3C/svg%3E") repeat;
|
|
border-color: rgba(14, 165, 233, 0.3);
|
|
}
|
|
|
|
/* Status bar: absolute overlay at very bottom */
|
|
.content :deep(.status-bar) {
|
|
position: absolute !important;
|
|
bottom: 0 !important;
|
|
left: 0 !important;
|
|
right: 0 !important;
|
|
z-index: 4 !important;
|
|
background: rgba(0, 6, 18, 0.6) !important;
|
|
backdrop-filter: blur(8px) !important;
|
|
-webkit-backdrop-filter: blur(8px) !important;
|
|
border-top: 1px solid rgba(255, 255, 255, 0.04) !important;
|
|
border-bottom: none !important;
|
|
padding: 0.15rem 0.5rem !important;
|
|
}
|
|
|
|
.content :deep(.status-id) {
|
|
color: rgba(255,255,255,0.35) !important;
|
|
}
|
|
|
|
.content :deep(.status-bar .copy-id-btn) {
|
|
color: rgba(255,255,255,0.25) !important;
|
|
}
|
|
|
|
.content :deep(.status-bar .copy-id-btn:hover) {
|
|
color: rgba(255,255,255,0.6) !important;
|
|
}
|
|
|
|
.content :deep(.status-bar .meta-badge) {
|
|
border-radius: 0 !important;
|
|
font-family: 'Courier New', monospace !important;
|
|
}
|
|
|
|
.content :deep(.status-bar .meta-badge.model) {
|
|
background: rgba(99, 102, 241, 0.1) !important;
|
|
color: #a5b4fc !important;
|
|
}
|
|
|
|
.content :deep(.status-bar .meta-badge.version) {
|
|
background: rgba(255,255,255,0.04) !important;
|
|
color: rgba(255,255,255,0.3) !important;
|
|
}
|
|
|
|
.content :deep(.status-bar .meta-count),
|
|
.content :deep(.status-bar .meta-duration) {
|
|
color: rgba(255,255,255,0.2) !important;
|
|
}
|
|
|
|
/* UserInput: absolute overlay above status bar */
|
|
.content :deep(.user-input) {
|
|
position: absolute !important;
|
|
bottom: 20px !important;
|
|
left: 0 !important;
|
|
right: 0 !important;
|
|
z-index: 3 !important;
|
|
background: rgba(0, 6, 18, 0.5) !important;
|
|
backdrop-filter: blur(8px) !important;
|
|
-webkit-backdrop-filter: blur(8px) !important;
|
|
border-top: 1px solid rgba(255, 255, 255, 0.06) !important;
|
|
padding: 0.3rem 0.5rem !important;
|
|
}
|
|
|
|
/* Dark overlay on input-container so text is readable */
|
|
.content :deep(.input-container) {
|
|
background: rgba(0, 6, 18, 0.8) !important;
|
|
border-color: rgba(14, 165, 233, 0.1) !important;
|
|
border-radius: 0 !important;
|
|
padding: 0.3rem 0.4rem !important;
|
|
}
|
|
|
|
.content :deep(.input-container:focus-within) {
|
|
border-color: rgba(14, 165, 233, 0.25) !important;
|
|
background: rgba(0, 6, 18, 0.85) !important;
|
|
}
|
|
|
|
.content :deep(.input-field) {
|
|
color: rgba(255,255,255,0.85);
|
|
font-family: 'Courier New', monospace;
|
|
font-size: 12px;
|
|
}
|
|
|
|
/* Send button: pixel art daytime ocean, no border */
|
|
.content :deep(.send-btn) {
|
|
background:
|
|
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='28' height='28' viewBox='0 0 28 28' shape-rendering='crispEdges'%3E%3Crect width='28' height='6' fill='%2387ceeb'/%3E%3Crect y='6' width='28' height='4' fill='%2356b3d9'/%3E%3Crect y='10' width='28' height='4' fill='%232d9abf'/%3E%3Crect y='14' width='28' height='4' fill='%231a7fa5'/%3E%3Crect y='18' width='28' height='4' fill='%23106888'/%3E%3Crect y='22' width='28' height='6' fill='%23c2b280'/%3E%3Crect x='24' y='4' width='3' height='3' fill='%23fffde0' opacity='0.8'/%3E%3Crect x='25' y='3' width='2' height='1' fill='%23fffde0' opacity='0.5'/%3E%3Crect x='2' y='5' width='4' height='2' fill='white' opacity='0.35'/%3E%3Crect x='10' y='4' width='6' height='2' fill='white' opacity='0.25'/%3E%3Crect x='20' y='6' width='3' height='1' fill='white' opacity='0.2'/%3E%3Crect x='5' y='12' width='3' height='2' fill='%23f97316' opacity='0.7'/%3E%3Crect x='4' y='13' width='1' height='1' fill='%23fdba74' opacity='0.5'/%3E%3Crect x='18' y='16' width='2' height='1' fill='%232563eb' opacity='0.5'/%3E%3Crect x='20' y='16' width='1' height='1' fill='%2393c5fd' opacity='0.4'/%3E%3Crect x='8' y='18' width='1' height='1' fill='white' opacity='0.2'/%3E%3Crect x='22' y='12' width='1' height='1' fill='white' opacity='0.2'/%3E%3Crect x='14' y='20' width='1' height='1' fill='white' opacity='0.15'/%3E%3Crect x='3' y='23' width='4' height='3' fill='%23059669' opacity='0.5'/%3E%3Crect x='4' y='22' width='2' height='1' fill='%2310b981' opacity='0.4'/%3E%3Crect x='20' y='24' width='3' height='2' fill='%23059669' opacity='0.4'/%3E%3Crect x='12' y='25' width='3' height='2' fill='%23ec4899' opacity='0.45'/%3E%3Crect x='13' y='24' width='2' height='1' fill='%23f472b6' opacity='0.35'/%3E%3C/svg%3E") !important;
|
|
border: none !important;
|
|
border-radius: 0 !important;
|
|
width: 28px !important;
|
|
height: 28px !important;
|
|
color: white !important;
|
|
image-rendering: pixelated;
|
|
box-shadow: none !important;
|
|
transition: color 0.15s ease !important;
|
|
}
|
|
|
|
.content :deep(.send-btn:hover:not(:disabled)) {
|
|
color: #fffde0 !important;
|
|
filter: none !important;
|
|
box-shadow: 0 0 12px rgba(135, 206, 235, 0.5), 0 0 4px rgba(255, 253, 224, 0.3) !important;
|
|
}
|
|
|
|
.content :deep(.send-btn:disabled) {
|
|
opacity: 0.25 !important;
|
|
filter: saturate(0.3) !important;
|
|
}
|
|
|
|
/* Selection bar */
|
|
.content :deep(.selection-bar) {
|
|
background: rgba(8, 8, 12, 0.92);
|
|
border-color: rgba(255,255,255,0.06);
|
|
border-radius: 0;
|
|
}
|
|
|
|
.content :deep(.selection-count) {
|
|
color: rgba(255,255,255,0.4);
|
|
font-family: 'Courier New', monospace;
|
|
}
|
|
|
|
.content :deep(.selection-btn.toggle-all) {
|
|
background: rgba(255,255,255,0.06);
|
|
color: rgba(255,255,255,0.5);
|
|
border-radius: 0;
|
|
}
|
|
|
|
.content :deep(.message-wrapper.selected) {
|
|
background: rgba(99, 102, 241, 0.06);
|
|
}
|
|
|
|
/* ── Empty State ── */
|
|
.empty-state {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
flex: 1;
|
|
gap: 8px;
|
|
color: rgba(255,255,255,0.3);
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
.empty-state svg {
|
|
opacity: 0.2;
|
|
}
|
|
|
|
.empty-state span {
|
|
font-size: 11px;
|
|
font-family: 'Courier New', monospace;
|
|
color: rgba(255,255,255,0.35);
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
.empty-state small {
|
|
font-size: 10px;
|
|
font-family: 'Courier New', monospace;
|
|
color: rgba(255,255,255,0.2);
|
|
}
|
|
|
|
/* ── Resize Handle ── */
|
|
.resize-handle {
|
|
position: absolute;
|
|
right: 0;
|
|
bottom: 0;
|
|
width: 16px;
|
|
height: 16px;
|
|
cursor: nwse-resize;
|
|
background:
|
|
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' shape-rendering='crispEdges'%3E%3Crect x='12' y='12' width='2' height='2' fill='%23818cf8' opacity='0.25'/%3E%3Crect x='8' y='12' width='2' height='2' fill='%23818cf8' opacity='0.15'/%3E%3Crect x='12' y='8' width='2' height='2' fill='%23818cf8' opacity='0.15'/%3E%3Crect x='4' y='12' width='2' height='2' fill='%23818cf8' opacity='0.08'/%3E%3Crect x='12' y='4' width='2' height='2' fill='%23818cf8' opacity='0.08'/%3E%3Crect x='8' y='8' width='2' height='2' fill='%23818cf8' opacity='0.08'/%3E%3C/svg%3E") no-repeat center center;
|
|
border-radius: 0 0 10px 0;
|
|
}
|
|
|
|
.resize-handle:hover {
|
|
background:
|
|
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' shape-rendering='crispEdges'%3E%3Crect x='12' y='12' width='2' height='2' fill='%23a5b4fc' opacity='0.4'/%3E%3Crect x='8' y='12' width='2' height='2' fill='%23a5b4fc' opacity='0.25'/%3E%3Crect x='12' y='8' width='2' height='2' fill='%23a5b4fc' opacity='0.25'/%3E%3Crect x='4' y='12' width='2' height='2' fill='%23a5b4fc' opacity='0.15'/%3E%3Crect x='12' y='4' width='2' height='2' fill='%23a5b4fc' opacity='0.15'/%3E%3Crect x='8' y='8' width='2' height='2' fill='%23a5b4fc' opacity='0.15'/%3E%3C/svg%3E") no-repeat center center;
|
|
}
|
|
|
|
.aero-win.resizing {
|
|
user-select: none;
|
|
}
|
|
|
|
.aero-win.resizing .content {
|
|
pointer-events: none;
|
|
}
|
|
|
|
/* ── Transitions ── */
|
|
.win-slide-enter-active,
|
|
.win-slide-leave-active {
|
|
transition: all .2s ease;
|
|
}
|
|
|
|
.win-slide-enter-from,
|
|
.win-slide-leave-to {
|
|
opacity: 0;
|
|
transform: translateY(16px) scale(0.98);
|
|
}
|
|
|
|
|
|
@keyframes spin {
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
|
|
/* ── Mobile ── */
|
|
.aero-win.mobile {
|
|
min-width: unset;
|
|
min-height: unset;
|
|
max-width: 100%;
|
|
transition: height 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
|
|
.aero-win.mobile .glass {
|
|
border-radius: 16px 16px 0 0;
|
|
padding-bottom: env(safe-area-inset-bottom, 0);
|
|
}
|
|
|
|
|
|
.aero-win.mobile .resize-handle {
|
|
display: none;
|
|
}
|
|
|
|
.aero-win.mobile .content {
|
|
flex: 1;
|
|
min-height: 100px;
|
|
touch-action: pan-y;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.aero-win.mobile.win-slide-enter-active {
|
|
transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1) !important;
|
|
}
|
|
|
|
.aero-win.mobile.win-slide-leave-active {
|
|
transition: transform 0.25s cubic-bezier(0.4, 0, 1, 1) !important;
|
|
}
|
|
|
|
.aero-win.mobile.win-slide-enter-from,
|
|
.aero-win.mobile.win-slide-leave-to {
|
|
transform: translateY(100%) !important;
|
|
opacity: 1 !important;
|
|
}
|
|
</style>
|