Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ Shared config in `~/.config/micyou/` (Linux) or `%APPDATA%\micyou` (Windows), re
- Every invoke is try/catch-wrapped with an optimistic update and rollback; connection errors funnel into `ConnectionErrorDialog` via `utils/connectionError.ts`
- UI-only prefs persist through `@vueuse/core` `useStorage` with `micyou_*` keys; anything CLI or TUI also reads belongs in the shared config files
- Windows are frameless and transparent with self-drawn chrome; mark glass panels with `data-blur-region` so `useWindowEffects` requests matching native blur
- Rounded window corners are per platform: DWM corner preference on Windows, NSWindow corner radius via vibrancy on macOS, and a `clip-path` on `body` only under `.platform-linux` — do not clip the document on Windows/macOS, the full-page clip mask made streaming animations stutter in WebView2
- Continuous animations (the streaming glow and status dot loops) are CSS keyframe animations on transform/opacity so the compositor drives them; keep JS animation libraries (anime.js) for one-shot interactions only
- The settings window is a separate webview (`features/settings/components/SettingsWindow.vue` plus one component per page under `sections/`); it shares state with the main window through `useStorage` storage events and `emitToWindow('main', …)`, drags from any non-control element marked `data-drag-surface`, and dialogs inside a section use `<Teleport to="body">` to escape the page transition
- Styling: the theme lives in the `@theme inline` block of `src/shared/assets/index.css` (no `tailwind.config.js` or PostCSS config); Material 3 HSL variables, 8 themes in light and dark via `.dark`; shadcn color names (`popover`, `accent`, `muted`, …) are aliased to Material 3 tokens; a scoped `<style>` using `@apply` needs `@reference "@/shared/assets/index.css";`; `cn()` = `twMerge(clsx(...))`
- `src/shared/components/ui/` holds only what is used (reka-ui `select`, `MD3Slider`, `MD3Switch`); use `MD3Switch` for toggles
Expand Down
61 changes: 4 additions & 57 deletions tauri-app/src/App.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, computed, watchEffect, watch, nextTick } from 'vue';
import { ref, onMounted, onUnmounted, computed, watch, nextTick } from 'vue';
import { useStorage, onClickOutside } from '@vueuse/core';
import { useI18n } from 'vue-i18n';

Expand Down Expand Up @@ -35,7 +35,7 @@ import MonitoringWarningDialog from './features/audio/components/MonitoringWarni

// Raw asset content and animation utilities
import appIconSvg from './shared/assets/app_icon.svg?raw';
import { animate, spring, utils, type JSAnimation } from 'animejs';
import { animate, spring } from 'animejs';

applyPlatformClasses();

Expand Down Expand Up @@ -70,11 +70,6 @@ const startDrag = async (e: MouseEvent) => {

// References to HTML elements for animations
const centralBtnRef = ref<HTMLButtonElement | null>(null);
const glowRef = ref<HTMLDivElement | null>(null);
const statusDotRef = ref<HTMLDivElement | null>(null);

let breatheAnim: JSAnimation | null = null;
let dotPulseAnim: JSAnimation | null = null;

// App wizard and pocket mode settings
const showOnboarding = ref(localStorage.getItem('micyou_onboarding_completed') !== 'true');
Expand Down Expand Up @@ -266,56 +261,8 @@ const onCentralBtnLeave = () => {
}
};

// Sets up dynamic breathing glow animation during active streams
watchEffect(() => {
if (server.serverState.value === 'streaming' && glowRef.value) {
if (!breatheAnim) {
breatheAnim = animate(glowRef.value, {
opacity: [0.3, 0.7],
scale: [1.2, 1.35],
duration: 2000,
alternate: true,
loop: true,
ease: 'inOutSine',
});
}
} else {
if (breatheAnim) {
breatheAnim.pause();
breatheAnim = null;
}
if (glowRef.value) {
utils.set(glowRef.value, { opacity: 0.3, scale: 1.25 });
}
}
});

// Sets up pulse animation on bottom bar indicator during active streams
watchEffect(() => {
if (server.serverState.value === 'streaming' && statusDotRef.value) {
if (!dotPulseAnim) {
dotPulseAnim = animate(statusDotRef.value, {
scale: [1, 1.4, 1],
duration: 1500,
loop: true,
ease: 'inOutQuad',
});
}
} else {
if (dotPulseAnim) {
dotPulseAnim.pause();
dotPulseAnim = null;
}
if (statusDotRef.value) {
utils.set(statusDotRef.value, { scale: 1 });
}
}
});

onUnmounted(() => {
stopPocketObserver();
if (breatheAnim) breatheAnim.pause();
if (dotPulseAnim) dotPulseAnim.pause();
});
</script>

Expand Down Expand Up @@ -528,7 +475,7 @@ onUnmounted(() => {
<AudioRing v-if="server.serverState.value === 'streaming'" :level="audio.audioLevel.value">
<!-- Central Button When Streaming -->
<div class="relative flex items-center justify-center">
<div ref="glowRef" class="absolute inset-0 bg-error/30 rounded-full blur-md"></div>
<div class="absolute inset-0 bg-error/30 rounded-full blur-md glow-breathe"></div>
<button ref="centralBtnRef" @click="toggleStreaming" @mouseenter="onCentralBtnHover" @mouseleave="onCentralBtnLeave" class="relative z-10 w-[72px] h-[72px] rounded-full bg-error flex items-center justify-center shadow-[0_0_16px_0] shadow-error/20 transition-all duration-300 group-hover:bg-error/90 border border-white/10 hover:shadow-[0_0_24px_4px] hover:shadow-error/30">
<Unlink class="w-7 h-7 text-on-error" stroke-width="2.5" />
</button>
Expand Down Expand Up @@ -557,7 +504,7 @@ onUnmounted(() => {
<!-- Bottom Bar -->
<div class="haze-surface rounded-2xl p-2 flex justify-between items-center shrink-0">
<div class="flex items-center px-3">
<div ref="statusDotRef" class="w-2 h-2 rounded-full mr-2" :class="server.serverState.value === 'streaming' ? 'bg-primary shadow-[0_0_8px_hsl(var(--primary))]' : (server.serverState.value === 'starting' ? 'bg-secondary animate-pulse shadow-[0_0_8px_hsl(var(--secondary))]' : (server.serverState.value === 'connecting' ? 'bg-tertiary animate-pulse shadow-[0_0_8px_hsl(var(--tertiary))]' : 'bg-on-surface-variant'))"></div>
<div class="w-2 h-2 rounded-full mr-2" :class="server.serverState.value === 'streaming' ? 'bg-primary dot-pulse shadow-[0_0_8px_hsl(var(--primary))]' : (server.serverState.value === 'starting' ? 'bg-secondary animate-pulse shadow-[0_0_8px_hsl(var(--secondary))]' : (server.serverState.value === 'connecting' ? 'bg-tertiary animate-pulse shadow-[0_0_8px_hsl(var(--tertiary))]' : 'bg-on-surface-variant'))"></div>
<span class="text-xs font-bold uppercase tracking-wider text-on-surface-variant transition-colors duration-300">{{ server.serverState.value === 'streaming' ? $t('app.status.stateStreaming') : (server.serverState.value === 'connecting' ? $t('app.status.stateConnecting') : (server.serverState.value === 'starting' ? $t('app.status.stateStarting') : $t('app.status.stateIdle'))) }}</span>
</div>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ onUnmounted(() => window.removeEventListener('message', handleMessage));
<template>
<div
ref="windowRoot"
class="h-screen w-screen overflow-hidden flex flex-col origin-center"
class="h-screen w-screen overflow-hidden rounded-2xl flex flex-col origin-center"
style="background: hsl(var(--surface))"
>
<header
Expand Down
34 changes: 32 additions & 2 deletions tauri-app/src/shared/assets/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,11 @@
cursor: pointer;
}

/* Windows are transparent and frameless; clip-path also clips fixed
overlays such as dialog backdrops, which overflow: hidden would not. */
body {
@apply bg-transparent text-foreground;
}

.platform-linux body {
clip-path: inset(0 round 1rem);
}

Expand Down Expand Up @@ -462,6 +463,35 @@
transform: translateY(-4px);
}

@keyframes glow-breathe {
from {
opacity: 0.3;
transform: scale(1.2);
}
to {
opacity: 0.7;
transform: scale(1.35);
}
}

@keyframes dot-pulse {
0%,
100% {
transform: scale(1);
}
50% {
transform: scale(1.4);
}
}

.glow-breathe {
animation: glow-breathe 2s cubic-bezier(0.37, 0, 0.63, 1) infinite alternate;
}

.dot-pulse {
animation: dot-pulse 1.5s cubic-bezier(0.45, 0, 0.55, 1) infinite;
}

/* Respect the system "reduce motion" setting everywhere. */
@media (prefers-reduced-motion: reduce) {
*,
Expand Down
Loading