refactor(frontend): retire legacy CSS and isolate visual artwork
This commit is contained in:
@@ -167,8 +167,10 @@ evidence; do not bundle them into tooling or structural refactors.
|
|||||||
`vocero-frontend/src/lib/messages.ts`. User text stays literal, and exports retain
|
`vocero-frontend/src/lib/messages.ts`. User text stays literal, and exports retain
|
||||||
raw message text. Keep sanitization and unsafe-link regression coverage when
|
raw message text. Keep sanitization and unsafe-link regression coverage when
|
||||||
changing it.
|
changing it.
|
||||||
- Reuse the existing design tokens, Tailwind integration, and
|
- Reuse the Tailwind tokens in `vocero-frontend/src/styles/theme.css` and shared
|
||||||
`vocero-frontend/src/styles/legacy-theme.css`. Verify both light and dark themes,
|
controls in `vocero-frontend/src/components/ui.tsx`. Keep specialized artwork
|
||||||
|
and sanitized Markdown styles scoped under `vocero-frontend/src/styles/`.
|
||||||
|
Verify both light and dark themes,
|
||||||
narrow screens, keyboard focus, dialog dismissal, and voice-state feedback for
|
narrow screens, keyboard focus, dialog dismissal, and voice-state feedback for
|
||||||
UI changes.
|
UI changes.
|
||||||
Preserve intentional `?debug=true` controls and browser audio-permission flows.
|
Preserve intentional `?debug=true` controls and browser audio-permission flows.
|
||||||
|
|||||||
@@ -57,8 +57,9 @@ audio. It is not an unconditional byte-for-byte tunnel.
|
|||||||
| [client.ts](../../vocero-frontend/src/realtime/client.ts) | Realtime protocol, session lifecycle, audio and transcript events |
|
| [client.ts](../../vocero-frontend/src/realtime/client.ts) | Realtime protocol, session lifecycle, audio and transcript events |
|
||||||
| [components](../../vocero-frontend/src/components) | Chat, caller profile, settings and voice components |
|
| [components](../../vocero-frontend/src/components) | Chat, caller profile, settings and voice components |
|
||||||
| [audio](../../vocero-frontend/src/audio) | TypeScript capture/playback worklets, PCM codec, and visualizer |
|
| [audio](../../vocero-frontend/src/audio) | TypeScript capture/playback worklets, PCM codec, and visualizer |
|
||||||
| [legacy-theme.css](../../vocero-frontend/src/styles/legacy-theme.css) | Theme tokens, voice artwork and component appearance |
|
| [theme.css](../../vocero-frontend/src/styles/theme.css) | Tailwind semantic tokens, configured theme variants and responsive variants |
|
||||||
| [layout.css](../../vocero-frontend/src/styles/layout.css) | Shared responsive layout, transcript sizing and reduced-motion behavior |
|
| [ui.tsx](../../vocero-frontend/src/components/ui.tsx) | Shared Tailwind buttons, fields and dialog controls; layout utilities live with components |
|
||||||
|
| [styles](../../vocero-frontend/src/styles) | Minimal base defaults, reduced motion, audio artwork, sanitized Markdown, decorative surfaces and native range controls |
|
||||||
|
|
||||||
## Server routes
|
## Server routes
|
||||||
|
|
||||||
|
|||||||
+37
-18
@@ -1,7 +1,7 @@
|
|||||||
# Design language
|
# Design language
|
||||||
|
|
||||||
This guide describes the current browser UI. Use
|
This guide describes the current browser UI. Use
|
||||||
[legacy-theme.css](../../vocero-frontend/src/styles/legacy-theme.css) as the token/selector
|
[theme.css](../../vocero-frontend/src/styles/theme.css) as the Tailwind token
|
||||||
reference and [App.tsx](../../vocero-frontend/src/App.tsx) for the theme-specific structure.
|
reference and [App.tsx](../../vocero-frontend/src/App.tsx) for the theme-specific structure.
|
||||||
Earlier documentation described a monochrome-only dark UI; the current design
|
Earlier documentation described a monochrome-only dark UI; the current design
|
||||||
also uses aurora gradients, glass surfaces, and a separate light theme.
|
also uses aurora gradients, glass surfaces, and a separate light theme.
|
||||||
@@ -10,8 +10,10 @@ also uses aurora gradients, glass surfaces, and a separate light theme.
|
|||||||
|
|
||||||
`S2S_THEME=white` selects the white/blue interface. Empty or `dark` selects the
|
`S2S_THEME=white` selects the white/blue interface. Empty or `dark` selects the
|
||||||
dark aurora interface. The browser sets `html[data-theme]` after configuration
|
dark aurora interface. The browser sets `html[data-theme]` after configuration
|
||||||
loads; an initial visibility guard prevents the wrong theme flashing on first
|
loads; React renders a loading screen until configuration resolves. Theme values
|
||||||
paint. Extend the existing theme selectors when adding components.
|
live under `:root` and `html[data-theme="white"]`, with `@theme inline` exposing
|
||||||
|
semantic utilities. The `white:` and `dark:` variants follow this attribute,
|
||||||
|
independently of the browser's preferred color scheme.
|
||||||
|
|
||||||
| Token group | Dark theme values/purpose |
|
| Token group | Dark theme values/purpose |
|
||||||
|---|---|
|
|---|---|
|
||||||
@@ -20,14 +22,20 @@ paint. Extend the existing theme selectors when adding components.
|
|||||||
| Text | `--text`, `--text-dim`, `--text-faint` |
|
| Text | `--text`, `--text-dim`, `--text-faint` |
|
||||||
| Borders | `--border` and `--border-strong` hairlines |
|
| Borders | `--border` and `--border-strong` hairlines |
|
||||||
| Brand | `--brand: #8b5cf6`, `--brand-2: #ec4899`, `--brand-3: #22d3ee` |
|
| Brand | `--brand: #8b5cf6`, `--brand-2: #ec4899`, `--brand-3: #22d3ee` |
|
||||||
| Radius | `--radius-sm: 10px`, `--radius-md: 16px`, `--radius-lg: 26px`, `--radius-xl: 34px` |
|
| Radius | `rounded-control: 10px`, `rounded-panel: 16px`, `rounded-card: 26px` (22px in white) |
|
||||||
| Shadows | `--shadow-soft`, `--shadow-glow-brand`, `--shadow-glass` |
|
| Shadows | `--shadow-soft`, `--shadow-glow-brand`, `--shadow-glass` |
|
||||||
|
|
||||||
|
Use semantic utilities such as `bg-surface`, `bg-surface-raised`, `text-foreground`,
|
||||||
|
`text-muted`, `border-line`, `rounded-control` and `shadow-panel`. Palette values
|
||||||
|
are runtime CSS variables; fonts are declared in `@theme`. Keep audio levels and
|
||||||
|
waveform bands outside the theme token registry.
|
||||||
|
|
||||||
Prefer existing variables and `color-mix` over new unrelated colors. The dark
|
Prefer existing variables and `color-mix` over new unrelated colors. The dark
|
||||||
theme's orb is the strongest visual focus, while gradients and restrained role
|
theme's orb is the strongest visual focus, while gradients and restrained role
|
||||||
tints also appear in the surrounding interface. The white theme changes
|
tints also appear in the surrounding interface. The white theme changes
|
||||||
surfaces, typography contrast, controls and chat presentation through its own
|
surfaces, typography contrast, controls and chat presentation through its own
|
||||||
selector overrides rather than a second stylesheet.
|
tokens and explicit component variants. The two voice presentations retain
|
||||||
|
their separate React markup and scoped artwork.
|
||||||
|
|
||||||
## Voice state
|
## Voice state
|
||||||
|
|
||||||
@@ -62,29 +70,40 @@ details out of ordinary user flows.
|
|||||||
## Layout and components
|
## Layout and components
|
||||||
|
|
||||||
The page uses a column layout: header, voice stage, always-visible inline
|
The page uses a column layout: header, voice stage, always-visible inline
|
||||||
conversation, and sticky footer. The conversation is no longer the old
|
conversation, and footer. The document scrolls naturally while the transcript
|
||||||
toggleable right overlay. Preserve space below the latest message so the sticky
|
has its own scroll area. New messages preserve the reader's position until they
|
||||||
footer does not obscure it; chat autoscroll retries after layout/reflow changes.
|
return to the bottom or use the jump-to-latest action.
|
||||||
|
|
||||||
In the dark theme, the reports sidebar is fixed on the left at desktop widths
|
In the dark theme, the reports sidebar is fixed on the left at desktop widths
|
||||||
of at least 1024px. On smaller screens it becomes an off-canvas panel with a
|
of at least 1024px. On smaller screens it becomes a native dialog drawer with a
|
||||||
toggle and backdrop. The white theme hides the sidebar, its toggle, and hint.
|
toggle and backdrop. The white theme omits the sidebar, its toggle, and hint.
|
||||||
The user avatar opens a profile dialog; profile fields collapse from two
|
The user avatar opens a profile dialog with label/value rows. Some controls are
|
||||||
columns on narrow screens. Some controls are intentionally gated by `?debug=true`.
|
intentionally gated by `?debug=true`.
|
||||||
|
|
||||||
|
Reuse [ui.tsx](../../vocero-frontend/src/components/ui.tsx) for buttons, icon buttons,
|
||||||
|
fields and dialog controls. Keep layout utilities with the owning component.
|
||||||
|
Use complete class names in conditional maps; interpolated utility names cannot
|
||||||
|
be detected reliably. `compact:` preserves the existing maximum width of 600px;
|
||||||
|
`short:` covers heights at or below 640px. Other artwork breakpoints remain local.
|
||||||
|
|
||||||
Use the existing button, icon-button, modal, switch and chat-message classes.
|
|
||||||
The dark theme uses glass bubbles with role tints, borders and restrained glow;
|
The dark theme uses glass bubbles with role tints, borders and restrained glow;
|
||||||
the light theme uses its white/blue message and avatar treatment. Keep required
|
the light theme uses its white/blue message and avatar treatment.
|
||||||
chat DOM elements intact; optional historical overlay controls may be absent.
|
|
||||||
|
The stylesheet entry point imports Tailwind's theme and utilities without
|
||||||
|
Preflight. Keep resets explicit in `base.css`; enabling Preflight is a separate
|
||||||
|
visual change. Custom CSS is reserved for the page background, audio artwork,
|
||||||
|
sanitized Markdown descendants, decorative masks and native range pseudo-elements.
|
||||||
|
Do not add model-supplied utility classes to the Markdown sanitizer.
|
||||||
|
|
||||||
## Motion and accessibility
|
## Motion and accessibility
|
||||||
|
|
||||||
Microphone RMS and assistant audio levels drive the voice visualization.
|
Microphone RMS and assistant audio levels drive the voice visualization.
|
||||||
Breathing, glow and decorative gradients should remain subordinate to speech
|
Breathing, glow and decorative gradients should remain subordinate to speech
|
||||||
feedback. `body.booting` suppresses initial transitions; new animation should
|
feedback. Preserve the reduced-motion rules in `base.css`: they disable both
|
||||||
respect that state and `prefers-reduced-motion`.
|
CSS animations and the audio-driven transform variables. Their `!important`
|
||||||
|
declarations intentionally override inline variables written by the audio engine.
|
||||||
|
|
||||||
For changes, verify both themes, narrow mobile layouts down to 360px, keyboard
|
For changes, verify both themes, narrow mobile layouts down to 320px, keyboard
|
||||||
focus, dialog dismissal, and readable contrast. Prefer touch targets of at least
|
focus, dialog dismissal, and readable contrast. Prefer touch targets of at least
|
||||||
44px and respect safe-area padding. A CSS guideline is not proof that every
|
44px and respect safe-area padding. A CSS guideline is not proof that every
|
||||||
existing control already meets the target.
|
existing control already meets the target.
|
||||||
|
|||||||
@@ -31,6 +31,11 @@ the implementation at the recorded date; current operating instructions live in
|
|||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
- Replace the frontend legacy stylesheet with Tailwind v4 semantic theme tokens,
|
||||||
|
shared form/dialog controls and component-owned layout utilities. Preserve both
|
||||||
|
configured themes, responsive layouts, sanitized Markdown and audio-driven
|
||||||
|
visuals in focused stylesheets, and remove unused historical selectors.
|
||||||
|
|
||||||
- Renamed the API and browser projects to `vocero-api/` and `vocero-frontend/`.
|
- Renamed the API and browser projects to `vocero-api/` and `vocero-frontend/`.
|
||||||
Python uses the `vocero-api` distribution and `vocero_api` import package;
|
Python uses the `vocero-api` distribution and `vocero_api` import package;
|
||||||
npm uses `vocero-frontend`. Updated the launcher, Docker setup, tooling,
|
npm uses `vocero-frontend`. Updated the launcher, Docker setup, tooling,
|
||||||
|
|||||||
@@ -131,10 +131,13 @@ Vitest handles Node 26's experimental web storage behavior internally.
|
|||||||
- `src/lib/`: validated backend contracts, settings, prompt context, profile
|
- `src/lib/`: validated backend contracts, settings, prompt context, profile
|
||||||
and report presentation, Markdown sanitization, and conversation exports.
|
and report presentation, Markdown sanitization, and conversation exports.
|
||||||
- `config/backend-proxy.ts`: the shared HTTP/WebSocket development proxy.
|
- `config/backend-proxy.ts`: the shared HTTP/WebSocket development proxy.
|
||||||
- `src/styles.css`: Tailwind integration and React-specific layout adjustments.
|
- `src/styles.css`: Tailwind layer/import entry point. Preflight remains disabled.
|
||||||
- `src/styles/legacy-theme.css`: the existing visual styles, retained for
|
- `src/styles/theme.css`: semantic Tailwind tokens and server-selected theme
|
||||||
fidelity to both the white microphone and dark orb themes. The UI is ported
|
variants. Use `bg-surface`, `text-foreground`, `border-line`, and related utilities.
|
||||||
to React; the full design has not been rewritten as Tailwind utilities.
|
- `src/components/ui.tsx`: shared buttons, icon buttons, fields and dialog controls.
|
||||||
|
Components own their layout, responsive utilities and interaction styling.
|
||||||
|
- `src/styles/`: minimal element defaults and reduced motion, voice artwork,
|
||||||
|
sanitized Markdown, decorative surfaces and native range pseudo-elements.
|
||||||
|
|
||||||
The current Spanish interface, local preference keys, session-only RAG,
|
The current Spanish interface, local preference keys, session-only RAG,
|
||||||
Markdown replies, JSON/TXT exports, optional browser wake word, TALK/TALKFIRST
|
Markdown replies, JSON/TXT exports, optional browser wake word, TALK/TALKFIRST
|
||||||
|
|||||||
@@ -118,13 +118,13 @@ export default function App() {
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
)}
|
)}
|
||||||
<div id="app" className={`flex w-full min-w-0 flex-1 flex-col ${hasReports ? "lg:pl-[320px]" : ""}`}>
|
<div id="app" className={`flex w-full min-w-0 flex-1 flex-col ${hasReports ? "lg:pl-[320px]" : ""}`}>
|
||||||
<header className="relative z-2 mt-[max(6px,calc(env(safe-area-inset-top,0px)*0.2))] flex w-full flex-none flex-wrap items-start justify-between gap-2 px-4 pt-4 white:static white:grid white:auto-rows-min white:grid-cols-[52px_minmax(0,1fr)_52px] white:justify-items-center white:gap-x-1 white:gap-y-0 white:px-2.5 white:pt-4 white:max-[480px]:px-1.5 compact:gap-3 [@media(max-height:560px)]:px-3.5 [@media(max-height:560px)]:pt-1.5 [@media(max-height:780px)]:px-5 [@media(max-height:780px)]:pt-3">
|
<header className="relative z-2 mt-[max(6px,calc(env(safe-area-inset-top,0px)*0.2))] flex w-full flex-none flex-wrap items-start justify-between gap-2 px-4 pt-4 white:static white:grid white:auto-rows-min white:grid-cols-[52px_minmax(0,1fr)_52px] white:justify-items-center white:gap-x-1 white:gap-y-0 white:px-2.5 white:pt-4 white:max-[480px]:px-1.5 dark:compact:gap-3 dark:[@media(560px<height<=780px)]:px-5 dark:[@media(560px<height<=780px)]:pt-3 dark:[@media(max-height:560px)]:px-3.5 dark:[@media(max-height:560px)]:pt-1.5">
|
||||||
<div className="brand-art relative flex min-w-0 flex-[1_1_360px] items-start gap-2.5 overflow-hidden rounded-panel border border-solid border-line px-3 py-2.5 text-[15px] font-semibold tracking-[0.01em] text-muted white:col-start-2 white:row-start-1 white:m-0 white:w-full white:flex-none white:justify-center white:rounded-none white:border-0 white:p-0 white:text-center compact:text-[14px]">
|
<div className="brand-art relative flex min-w-0 flex-[1_1_360px] items-start gap-2.5 overflow-hidden rounded-panel border border-solid border-line px-3 py-2.5 text-[15px] font-semibold tracking-[0.01em] text-muted white:col-start-2 white:row-start-1 white:m-0 white:w-full white:flex-none white:justify-center white:rounded-none white:border-0 white:p-0 white:text-center compact:text-[14px]">
|
||||||
<div className="flex w-full min-w-0 items-center justify-between gap-3 white:flex-col white:justify-center white:gap-0.5 white:px-1">
|
<div className="flex w-full min-w-0 items-center justify-between gap-3 white:flex-col white:justify-center white:gap-0.5 white:px-1">
|
||||||
<div className="flex items-center gap-[9px] white:mx-auto white:w-full white:flex-col white:justify-center white:gap-0.5">
|
<div className="flex items-center gap-[9px] white:mx-auto white:w-full white:flex-col white:justify-center white:gap-0.5">
|
||||||
<h1 className="m-0 leading-none">
|
<h1 className="m-0 leading-none">
|
||||||
<a
|
<a
|
||||||
className="block border-0 bg-(image:--brand-gradient) bg-clip-text text-[24px] leading-[1.1] font-semibold tracking-[0.005em] text-transparent hover:underline hover:underline-offset-2 white:bg-none white:text-[clamp(24px,6vw,32px)] white:leading-[1.05] white:font-extrabold white:tracking-[-0.02em] white:whitespace-nowrap white:text-foreground compact:text-[22px] [@media(max-height:560px)]:text-[17px] [@media(max-height:780px)]:text-[20px]"
|
className="block border-0 bg-(image:--brand-gradient) bg-clip-text text-[24px] leading-[1.1] font-semibold tracking-[0.005em] text-transparent hover:underline hover:underline-offset-2 white:bg-none white:text-[clamp(24px,6vw,32px)] white:leading-[1.05] white:font-extrabold white:tracking-[-0.02em] white:whitespace-nowrap white:text-foreground dark:compact:text-[22px] dark:[@media(560px<height<=780px)]:text-[20px] dark:[@media(max-height:560px)]:text-[17px]"
|
||||||
href={window.location.pathname + window.location.search}
|
href={window.location.pathname + window.location.search}
|
||||||
>
|
>
|
||||||
Vocero
|
Vocero
|
||||||
|
|||||||
@@ -73,10 +73,11 @@ export function SettingsDialog({ state, controller, onClose }: Props) {
|
|||||||
{settings.noiseGate <= GATE_OFF ? "Desactivada" : `${String(settings.noiseGate)} dB`}
|
{settings.noiseGate <= GATE_OFF ? "Desactivada" : `${String(settings.noiseGate)} dB`}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<div className="gate">
|
<div className="flex flex-col gap-1">
|
||||||
<div className="gate-track">
|
<div className="relative flex h-3.5 items-center before:absolute before:inset-x-0 before:h-2 before:rounded-full before:border before:border-solid before:border-line before:bg-surface before:content-['']">
|
||||||
<GateMeter controller={controller} />
|
<GateMeter controller={controller} />
|
||||||
<input
|
<input
|
||||||
|
className="gate-range relative z-1 m-0 h-3.5 w-full cursor-pointer appearance-none border-0 bg-transparent p-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"
|
||||||
id="noise-gate"
|
id="noise-gate"
|
||||||
aria-label="Umbral de la puerta de ruido"
|
aria-label="Umbral de la puerta de ruido"
|
||||||
type="range"
|
type="range"
|
||||||
@@ -89,7 +90,7 @@ export function SettingsDialog({ state, controller, onClose }: Props) {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="gate-ends">
|
<div className="flex justify-between text-[11px] text-faint">
|
||||||
<span>Desactivada</span>
|
<span>Desactivada</span>
|
||||||
<span>−3 dB</span>
|
<span>−3 dB</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -151,7 +152,13 @@ function GateMeter({ controller }: { controller: VoiceController }) {
|
|||||||
0,
|
0,
|
||||||
Math.min(100, ((20 * Math.log10(level || 0.00001) - GATE_OFF) * 100) / (GATE_MAX - GATE_OFF)),
|
Math.min(100, ((20 * Math.log10(level || 0.00001) - GATE_OFF) * 100) / (GATE_MAX - GATE_OFF)),
|
||||||
);
|
);
|
||||||
return <div className="gate-meter-fill" style={{ width: `${String(meter)}%` }} aria-hidden="true" />;
|
return (
|
||||||
|
<div
|
||||||
|
className="pointer-events-none absolute top-1/2 left-px h-1.5 -translate-y-1/2 rounded-full bg-accent-secondary transition-[width] duration-[60ms] ease-linear"
|
||||||
|
style={{ width: `${String(meter)}%` }}
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RagDialog({ state, controller, onClose }: Props) {
|
export function RagDialog({ state, controller, onClose }: Props) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useEffect, useState, useSyncExternalStore, type CSSProperties, type Poi
|
|||||||
import type { ConnectionStatus } from "../lib/contracts";
|
import type { ConnectionStatus } from "../lib/contracts";
|
||||||
import { GATE_MAX, GATE_OFF } from "../lib/settings";
|
import { GATE_MAX, GATE_OFF } from "../lib/settings";
|
||||||
import { ACTIVE_STATES, LIVE_STATES, type AppState, type VoiceController } from "../realtime/controller";
|
import { ACTIVE_STATES, LIVE_STATES, type AppState, type VoiceController } from "../realtime/controller";
|
||||||
|
import { Button } from "./ui";
|
||||||
|
|
||||||
const CAPTIONS: Record<ConnectionStatus, string> = {
|
const CAPTIONS: Record<ConnectionStatus, string> = {
|
||||||
idle: "Toca para empezar",
|
idle: "Toca para empezar",
|
||||||
@@ -41,7 +42,7 @@ function GateArc({
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
className="mic-gate-arc"
|
className="mic-gate-arc rounded-full focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-accent-secondary"
|
||||||
viewBox="0 0 100 100"
|
viewBox="0 0 100 100"
|
||||||
role="slider"
|
role="slider"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
@@ -179,9 +180,13 @@ export function VoiceStage({ state, controller }: { state: AppState; controller:
|
|||||||
};
|
};
|
||||||
const disabled = !ready || (active && status !== "your-turn");
|
const disabled = !ready || (active && status !== "your-turn");
|
||||||
return (
|
return (
|
||||||
<main className="stage">
|
<main className="stage relative z-1 flex min-h-0 flex-none flex-col items-center justify-start gap-3 px-3 pt-3 pb-2 short:py-2">
|
||||||
{white ? (
|
{white ? (
|
||||||
<div className={`white-mic-stage ${live ? "live" : ""}`} role="group" aria-label="Control de voz">
|
<div
|
||||||
|
className={`white-mic-stage relative flex flex-col items-center gap-4 pt-[22px] pb-1.5 compact:gap-3 compact:pt-4 short:gap-2 short:pt-2 ${live ? "live" : ""}`}
|
||||||
|
role="group"
|
||||||
|
aria-label="Control de voz"
|
||||||
|
>
|
||||||
<Waveform side="left" />
|
<Waveform side="left" />
|
||||||
<button
|
<button
|
||||||
id="white-mic-circle"
|
id="white-mic-circle"
|
||||||
@@ -198,15 +203,24 @@ export function VoiceStage({ state, controller }: { state: AppState; controller:
|
|||||||
<span className="wmc-icon wmc-spinner" aria-hidden="true" />
|
<span className="wmc-icon wmc-spinner" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
<Waveform side="right" />
|
<Waveform side="right" />
|
||||||
<p className="white-mic-caption" role="status">
|
<p
|
||||||
|
className="m-0 text-center text-[clamp(17px,4.2vw,20px)] leading-[1.25] font-semibold tracking-[-0.005em] text-accent short:text-[16px]"
|
||||||
|
role="status"
|
||||||
|
>
|
||||||
{caption}
|
{caption}
|
||||||
</p>
|
</p>
|
||||||
{state.notice && <p className="white-mic-subcaption">{state.notice}</p>}
|
{state.notice && (
|
||||||
|
<p className="m-0 max-w-[440px] text-center text-[13px] leading-[1.5] text-slate-600">{state.notice}</p>
|
||||||
|
)}
|
||||||
{active && (
|
{active && (
|
||||||
<div className="white-session-controls" role="group" aria-label="Controles de la conversación">
|
<div
|
||||||
|
className="flex max-w-full flex-wrap items-center justify-center gap-2.5 compact:gap-2 [&_svg]:size-[18px] [&_svg]:shrink-0"
|
||||||
|
role="group"
|
||||||
|
aria-label="Controles de la conversación"
|
||||||
|
>
|
||||||
{live && (
|
{live && (
|
||||||
<button
|
<button
|
||||||
className={`white-mute-btn ${state.muted ? "muted" : ""}`}
|
className="inline-flex min-h-11 cursor-pointer items-center justify-center gap-2 rounded-full border border-solid border-slate-300 bg-surface px-4 py-2.5 text-[clamp(14px,3.4vw,16px)] font-semibold text-speaking transition-colors hover:border-blue-300 hover:bg-blue-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent aria-pressed:border-amber-300 aria-pressed:bg-amber-50 aria-pressed:text-amber-900 aria-pressed:hover:bg-amber-100 compact:gap-1.5 compact:px-2.5 compact:py-2 compact:text-[13px]"
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
controller.toggleMuted();
|
controller.toggleMuted();
|
||||||
@@ -219,7 +233,7 @@ export function VoiceStage({ state, controller }: { state: AppState; controller:
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
className="white-stop-btn"
|
className="inline-flex min-h-11 cursor-pointer items-center gap-2.5 rounded-full border-0 bg-slate-200 py-2.5 pr-[22px] pl-4 text-[clamp(14px,3.4vw,16px)] font-semibold tracking-[-0.005em] text-foreground shadow-[0_2px_8px_rgb(15_23_42/0.06)] transition-colors hover:bg-slate-300 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent compact:gap-1.5 compact:px-2.5 compact:py-2 compact:text-[13px]"
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
void controller.stop();
|
void controller.stop();
|
||||||
@@ -233,7 +247,9 @@ export function VoiceStage({ state, controller }: { state: AppState; controller:
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className={`orb-wrap ${active ? "live" : ""}`}>
|
<div
|
||||||
|
className={`orb-wrap relative flex items-center justify-center gap-[clamp(18px,3vw,32px)] compact:flex-row compact:gap-[18px] ${active ? "live" : ""}`}
|
||||||
|
>
|
||||||
<MicrophoneGate
|
<MicrophoneGate
|
||||||
controller={controller}
|
controller={controller}
|
||||||
threshold={state.settings.noiseGate}
|
threshold={state.settings.noiseGate}
|
||||||
@@ -290,32 +306,29 @@ export function VoiceStage({ state, controller }: { state: AppState; controller:
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{(status === "queued" || status === "your-turn") && (
|
{(status === "queued" || status === "your-turn") && (
|
||||||
<div className="queue-actions">
|
<div className="flex flex-wrap items-center justify-center gap-3 [&_.circle-subcaption]:basis-full">
|
||||||
{status === "queued" ? (
|
{status === "queued" ? (
|
||||||
<p className="circle-subcaption">Posición en la cola: {state.queuePosition}</p>
|
<p className="circle-subcaption">Posición en la cola: {state.queuePosition}</p>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<JoinCountdown deadline={state.joinDeadline} />
|
<JoinCountdown deadline={state.joinDeadline} />
|
||||||
<button
|
<Button
|
||||||
className="join-queue-btn"
|
variant="primary"
|
||||||
type="button"
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
controller.join();
|
controller.join();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Entrar ahora
|
Entrar ahora
|
||||||
</button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<button
|
<Button
|
||||||
className="leave-queue-btn"
|
|
||||||
type="button"
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
void controller.stop();
|
void controller.stop();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Salir de la cola
|
Salir de la cola
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -1,154 +1,9 @@
|
|||||||
@layer theme, base, components, utilities;
|
@layer theme, base, components, utilities;
|
||||||
@import "tailwindcss/theme.css" layer(theme);
|
@import "tailwindcss/theme.css" layer(theme);
|
||||||
@import "./styles/theme.css";
|
@import "./styles/theme.css";
|
||||||
@import "./styles/legacy-theme.css" layer(components);
|
@import "./styles/base.css" layer(base);
|
||||||
@import "./styles/layout.css" layer(components);
|
@import "./styles/voice-stage.css" layer(components);
|
||||||
|
@import "./styles/markdown.css" layer(components);
|
||||||
@import "./styles/surfaces.css" layer(components);
|
@import "./styles/surfaces.css" layer(components);
|
||||||
|
@import "./styles/range.css" layer(components);
|
||||||
@import "tailwindcss/utilities.css" layer(utilities);
|
@import "tailwindcss/utilities.css" layer(utilities);
|
||||||
|
|
||||||
@layer components {
|
|
||||||
.chat-empty,
|
|
||||||
.chat-empty-hint {
|
|
||||||
opacity: 1;
|
|
||||||
color: var(--text-dim);
|
|
||||||
}
|
|
||||||
html[data-theme="white"] .chat-empty,
|
|
||||||
html[data-theme="white"] .chat-empty-hint {
|
|
||||||
color: #64748b;
|
|
||||||
}
|
|
||||||
#root {
|
|
||||||
display: contents;
|
|
||||||
}
|
|
||||||
.state-idle .ind-mic {
|
|
||||||
opacity: 1;
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
html[data-theme="white"] .topbar-right[data-debug-enabled="true"] {
|
|
||||||
display: flex;
|
|
||||||
grid-column: 1 / -1;
|
|
||||||
grid-row: 2;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
|
||||||
margin-top: 12px;
|
|
||||||
}
|
|
||||||
html[data-theme="white"] .white-mic-circle svg.wmc-mic {
|
|
||||||
fill: none;
|
|
||||||
}
|
|
||||||
.loading-screen {
|
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
display: grid;
|
|
||||||
place-items: center;
|
|
||||||
background: white;
|
|
||||||
color: #334155;
|
|
||||||
font: 500 16px system-ui;
|
|
||||||
z-index: 1000;
|
|
||||||
}
|
|
||||||
.startup-error {
|
|
||||||
max-width: 32rem;
|
|
||||||
padding: 24px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.app-error {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
margin: 12px auto;
|
|
||||||
padding: 10px 16px;
|
|
||||||
max-width: 680px;
|
|
||||||
border: 1px solid var(--error);
|
|
||||||
border-radius: 12px;
|
|
||||||
font-size: 13px;
|
|
||||||
background: var(--bg-elev);
|
|
||||||
}
|
|
||||||
.agent-tool-item {
|
|
||||||
padding: 12px 0;
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
overflow-wrap: anywhere;
|
|
||||||
}
|
|
||||||
.queue-actions {
|
|
||||||
align-items: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
.queue-actions .circle-subcaption {
|
|
||||||
flex-basis: 100%;
|
|
||||||
}
|
|
||||||
.reports-panel {
|
|
||||||
position: fixed;
|
|
||||||
inset: 0 auto 0 0;
|
|
||||||
width: 320px;
|
|
||||||
overflow-y: auto;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 20px;
|
|
||||||
background: var(--bg-elev);
|
|
||||||
border-right: 1px solid var(--border);
|
|
||||||
z-index: 3;
|
|
||||||
}
|
|
||||||
.reports-panel h2 {
|
|
||||||
margin: 0 0 16px;
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
.reports-drawer.modal {
|
|
||||||
margin: 0;
|
|
||||||
width: min(360px, 92vw);
|
|
||||||
max-height: 100dvh;
|
|
||||||
height: 100dvh;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
.reports-content .reports-hint {
|
|
||||||
margin: 0;
|
|
||||||
max-height: none;
|
|
||||||
padding: 12px;
|
|
||||||
}
|
|
||||||
.wake-word-control {
|
|
||||||
display: grid;
|
|
||||||
gap: 4px;
|
|
||||||
padding: 0 16px 12px;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
.wake-word-control label {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 8px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.wake-word-control span {
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--text-dim);
|
|
||||||
}
|
|
||||||
html[data-theme="white"] .wake-word-control span {
|
|
||||||
color: #64748b;
|
|
||||||
}
|
|
||||||
.bubble-body {
|
|
||||||
max-height: 240px;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
.white-session-controls svg {
|
|
||||||
width: 18px;
|
|
||||||
height: 18px;
|
|
||||||
}
|
|
||||||
.mic-gate-arc:focus-visible {
|
|
||||||
outline: 2px solid var(--accent-2);
|
|
||||||
outline-offset: 4px;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
.user-profile-row {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 90px 1fr;
|
|
||||||
gap: 16px;
|
|
||||||
padding: 12px 0;
|
|
||||||
}
|
|
||||||
.user-profile-value {
|
|
||||||
margin: 0;
|
|
||||||
overflow-wrap: anywhere;
|
|
||||||
}
|
|
||||||
.user-profile-label {
|
|
||||||
color: var(--text-dim);
|
|
||||||
}
|
|
||||||
.hist-tool-block {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,128 @@
|
|||||||
|
/* Minimal element defaults and page background. Preflight is intentionally not enabled. */
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: auto;
|
||||||
|
padding: max(8px, env(safe-area-inset-top)) max(8px, env(safe-area-inset-right)) 0 max(8px, env(safe-area-inset-left));
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
color: var(--text);
|
||||||
|
min-height: 100vh;
|
||||||
|
min-height: 100svh;
|
||||||
|
min-height: 100dvh;
|
||||||
|
width: 100%;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
position: relative;
|
||||||
|
background:
|
||||||
|
radial-gradient(1200px 800px at 15% -10%, rgba(139, 92, 246, 0.3) 0%, transparent 60%),
|
||||||
|
radial-gradient(900px 700px at 110% 10%, rgba(236, 72, 153, 0.22) 0%, transparent 55%),
|
||||||
|
radial-gradient(1000px 800px at 50% 120%, rgba(34, 211, 238, 0.22) 0%, transparent 60%),
|
||||||
|
radial-gradient(ellipse at 50% 50%, #0e1019 0%, var(--bg) 65%);
|
||||||
|
background-attachment: fixed;
|
||||||
|
}
|
||||||
|
body::before {
|
||||||
|
content: "";
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
background-image:
|
||||||
|
radial-gradient(rgba(255, 255, 255, 0.045) 1px, transparent 1px),
|
||||||
|
radial-gradient(rgba(139, 92, 246, 0.035) 1px, transparent 1px);
|
||||||
|
background-size:
|
||||||
|
42px 42px,
|
||||||
|
42px 42px;
|
||||||
|
background-position:
|
||||||
|
0 0,
|
||||||
|
21px 21px;
|
||||||
|
mix-blend-mode: screen;
|
||||||
|
opacity: 0.55;
|
||||||
|
mask-image: linear-gradient(180deg, rgba(0, 0, 0, 0.9) 0%, rgba(0, 0, 0, 0.4) 70%, transparent 100%);
|
||||||
|
}
|
||||||
|
body::after {
|
||||||
|
content: "";
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
background:
|
||||||
|
radial-gradient(600px 600px at 20% 80%, rgba(139, 92, 246, 0.1), transparent 70%),
|
||||||
|
radial-gradient(500px 500px at 80% 20%, rgba(34, 211, 238, 0.08), transparent 70%);
|
||||||
|
animation: auroraShift 22s ease-in-out infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--text);
|
||||||
|
text-decoration: none;
|
||||||
|
border-bottom: 1px solid var(--border-strong);
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
border-bottom-color: var(--text);
|
||||||
|
}
|
||||||
|
html[data-theme="white"] body {
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
background-image: none;
|
||||||
|
}
|
||||||
|
html[data-theme="white"] body::before,
|
||||||
|
html[data-theme="white"] body::after {
|
||||||
|
display: none;
|
||||||
|
background-image: none;
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
#root {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
@keyframes auroraShift {
|
||||||
|
0% {
|
||||||
|
transform: translate3d(0, 0, 0) scale(1);
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translate3d(3%, -2%, 0) scale(1.08);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translate3d(-2%, 2%, 0) scale(1.02);
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
:root {
|
||||||
|
--audio-level: 0 !important;
|
||||||
|
--ai-audio-level: 0 !important;
|
||||||
|
--bar0: 0 !important;
|
||||||
|
--bar1: 0 !important;
|
||||||
|
--bar2: 0 !important;
|
||||||
|
--bar3: 0 !important;
|
||||||
|
--bar4: 0 !important;
|
||||||
|
}
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
animation: none !important;
|
||||||
|
transition: none !important;
|
||||||
|
scroll-behavior: auto !important;
|
||||||
|
}
|
||||||
|
.circle.state-ai-speaking .bar,
|
||||||
|
.circle.state-user-speaking .bar {
|
||||||
|
transform: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,298 +0,0 @@
|
|||||||
/* Shared layout. Theme colors and voice artwork remain in legacy-theme.css. */
|
|
||||||
body {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: auto;
|
|
||||||
min-height: 100dvh;
|
|
||||||
padding: max(8px, env(safe-area-inset-top)) max(8px, env(safe-area-inset-right)) 0 max(8px, env(safe-area-inset-left));
|
|
||||||
}
|
|
||||||
#app {
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
flex-direction: column;
|
|
||||||
width: 100%;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
.stage,
|
|
||||||
html[data-theme="white"] .stage {
|
|
||||||
padding: 12px 12px 8px;
|
|
||||||
gap: 12px;
|
|
||||||
flex: none;
|
|
||||||
}
|
|
||||||
.chat-panel {
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
display: flex;
|
|
||||||
flex: 1 0 auto;
|
|
||||||
flex-direction: column;
|
|
||||||
height: clamp(240px, 45dvh, 640px);
|
|
||||||
min-width: 0;
|
|
||||||
margin: 8px 12px 0;
|
|
||||||
}
|
|
||||||
.circle-caption {
|
|
||||||
color: var(--text-dim);
|
|
||||||
}
|
|
||||||
.chat-panel-inner {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
min-height: 0;
|
|
||||||
max-width: 920px;
|
|
||||||
margin-inline: auto;
|
|
||||||
padding: 12px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.chat-panel-header {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 0 0 10px;
|
|
||||||
flex: none;
|
|
||||||
}
|
|
||||||
.chat-panel-header h2 {
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
.chat-header-actions,
|
|
||||||
html[data-theme="white"] .chat-header-actions {
|
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
.chat-export-btn {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
}
|
|
||||||
.chat-history {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex: 1;
|
|
||||||
min-height: 0;
|
|
||||||
gap: 14px;
|
|
||||||
padding: 12px 4px;
|
|
||||||
overflow-y: auto;
|
|
||||||
overflow-x: hidden;
|
|
||||||
overflow-anchor: none;
|
|
||||||
overscroll-behavior: contain;
|
|
||||||
scrollbar-width: thin;
|
|
||||||
}
|
|
||||||
.chat-history:focus-visible {
|
|
||||||
outline: 2px solid var(--accent);
|
|
||||||
outline-offset: -2px;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
.hist-msg {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex-shrink: 0;
|
|
||||||
gap: 6px;
|
|
||||||
max-width: 86%;
|
|
||||||
min-width: 0;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.hist-msg.user {
|
|
||||||
align-self: flex-start;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
.hist-msg.assistant {
|
|
||||||
align-self: flex-end;
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
.hist-msg.tool {
|
|
||||||
align-self: flex-start;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
.hist-meta {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: baseline;
|
|
||||||
gap: 4px 10px;
|
|
||||||
color: var(--text-dim);
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
.hist-meta span {
|
|
||||||
overflow-wrap: anywhere;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.hist-meta time {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
.hist-body {
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
.hist-body:not(.markdown-content) {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
.jump-to-latest {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
position: absolute;
|
|
||||||
bottom: 18px;
|
|
||||||
right: 18px;
|
|
||||||
z-index: 2;
|
|
||||||
gap: 6px;
|
|
||||||
box-shadow: var(--shadow-soft);
|
|
||||||
}
|
|
||||||
html[data-theme="white"] .chat-panel {
|
|
||||||
background: #fff;
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: 20px;
|
|
||||||
box-shadow: var(--shadow-soft);
|
|
||||||
}
|
|
||||||
html[data-theme="white"] .chat-panel-header {
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
}
|
|
||||||
html[data-theme="white"] .chat-history .hist-msg {
|
|
||||||
max-width: calc(100% - 40px);
|
|
||||||
padding-top: 0;
|
|
||||||
}
|
|
||||||
html[data-theme="white"] .chat-history .hist-msg.assistant {
|
|
||||||
align-self: flex-start;
|
|
||||||
margin: 4px auto 4px 40px;
|
|
||||||
}
|
|
||||||
html[data-theme="white"] .chat-history .hist-msg.user {
|
|
||||||
align-self: flex-end;
|
|
||||||
margin: 4px 40px 4px auto;
|
|
||||||
}
|
|
||||||
html[data-theme="white"] .chat-history .hist-msg.tool {
|
|
||||||
max-width: 100%;
|
|
||||||
margin: 4px 0;
|
|
||||||
}
|
|
||||||
html[data-theme="white"] .chat-history .hist-msg::before {
|
|
||||||
width: 28px;
|
|
||||||
height: 28px;
|
|
||||||
font-size: 11px;
|
|
||||||
}
|
|
||||||
html[data-theme="white"] .hist-meta {
|
|
||||||
color: #64748b;
|
|
||||||
}
|
|
||||||
html[data-theme="white"] .hist-msg.user .hist-meta {
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
.app-footer {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 16px 12px max(12px, env(safe-area-inset-bottom));
|
|
||||||
flex: none;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.feedback-link {
|
|
||||||
font-size: 13px;
|
|
||||||
color: var(--text-dim);
|
|
||||||
text-decoration: underline;
|
|
||||||
text-underline-offset: 3px;
|
|
||||||
border: 0;
|
|
||||||
padding: 4px;
|
|
||||||
}
|
|
||||||
.feedback-link:hover {
|
|
||||||
color: var(--text);
|
|
||||||
}
|
|
||||||
.feedback-link:focus-visible {
|
|
||||||
outline: 2px solid var(--accent);
|
|
||||||
outline-offset: 3px;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
#app.has-reports {
|
|
||||||
padding-left: 320px;
|
|
||||||
}
|
|
||||||
body:has(#app.has-reports) .app-footer {
|
|
||||||
margin-left: 320px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media (max-width: 600px) {
|
|
||||||
.topbar {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
.topbar-right {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
.chat-panel {
|
|
||||||
margin-inline: 0;
|
|
||||||
}
|
|
||||||
.chat-panel-inner {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
html[data-theme="white"] .chat-panel-header h2 {
|
|
||||||
font-size: 17px;
|
|
||||||
}
|
|
||||||
html[data-theme="white"] .chat-status-pill {
|
|
||||||
font-size: 11px;
|
|
||||||
padding: 5px 7px;
|
|
||||||
}
|
|
||||||
.orb-wrap {
|
|
||||||
flex-direction: row;
|
|
||||||
gap: 18px;
|
|
||||||
}
|
|
||||||
.circle {
|
|
||||||
width: clamp(110px, 32vw, 176px);
|
|
||||||
}
|
|
||||||
.orb-wrap.live .side-btn {
|
|
||||||
width: 44px;
|
|
||||||
height: 44px;
|
|
||||||
}
|
|
||||||
.mic-gate-arc {
|
|
||||||
width: 62px;
|
|
||||||
height: 62px;
|
|
||||||
}
|
|
||||||
html[data-theme="white"] .white-mic-stage {
|
|
||||||
gap: 12px;
|
|
||||||
padding-top: 16px;
|
|
||||||
}
|
|
||||||
html[data-theme="white"] .white-waveform {
|
|
||||||
top: 74px;
|
|
||||||
}
|
|
||||||
html[data-theme="white"] .white-session-controls {
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
html[data-theme="white"] .white-mute-btn,
|
|
||||||
html[data-theme="white"] .white-stop-btn {
|
|
||||||
font-size: 13px;
|
|
||||||
padding: 8px 10px;
|
|
||||||
min-height: 44px;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media (max-height: 640px) {
|
|
||||||
.stage,
|
|
||||||
html[data-theme="white"] .stage {
|
|
||||||
padding-block: 8px;
|
|
||||||
}
|
|
||||||
html[data-theme="white"] .white-mic-stage {
|
|
||||||
padding-top: 8px;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
html[data-theme="white"] .white-waveform {
|
|
||||||
top: 57px;
|
|
||||||
}
|
|
||||||
html[data-theme="white"] .white-mic-caption {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
|
||||||
:root {
|
|
||||||
--audio-level: 0 !important;
|
|
||||||
--ai-audio-level: 0 !important;
|
|
||||||
--bar0: 0 !important;
|
|
||||||
--bar1: 0 !important;
|
|
||||||
--bar2: 0 !important;
|
|
||||||
--bar3: 0 !important;
|
|
||||||
--bar4: 0 !important;
|
|
||||||
}
|
|
||||||
*,
|
|
||||||
*::before,
|
|
||||||
*::after {
|
|
||||||
animation: none !important;
|
|
||||||
transition: none !important;
|
|
||||||
scroll-behavior: auto !important;
|
|
||||||
}
|
|
||||||
.circle.state-ai-speaking .bar,
|
|
||||||
.circle.state-user-speaking .bar {
|
|
||||||
transform: none !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,123 @@
|
|||||||
|
/* Scoped styles for sanitized HTML produced by Marked; model content never supplies utility classes. */
|
||||||
|
|
||||||
|
.markdown-content {
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
white-space: normal;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
.markdown-content > :first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
.markdown-content > :last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.markdown-content p {
|
||||||
|
margin: 0 0 0.75em;
|
||||||
|
}
|
||||||
|
.markdown-content strong {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.markdown-content :is(h1, h2, h3, h4, h5, h6) {
|
||||||
|
margin: 1em 0 0.5em;
|
||||||
|
font-size: 1.12em;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
.markdown-content :is(ul, ol) {
|
||||||
|
margin: 0.65em 0;
|
||||||
|
padding-inline-start: 1.5em;
|
||||||
|
}
|
||||||
|
.markdown-content ul {
|
||||||
|
list-style-type: disc;
|
||||||
|
}
|
||||||
|
.markdown-content ol {
|
||||||
|
list-style-type: decimal;
|
||||||
|
}
|
||||||
|
.markdown-content li + li {
|
||||||
|
margin-top: 0.35em;
|
||||||
|
}
|
||||||
|
.markdown-content li > p {
|
||||||
|
margin: 0.25em 0;
|
||||||
|
}
|
||||||
|
.markdown-content li :is(ul, ol) {
|
||||||
|
margin: 0.3em 0;
|
||||||
|
}
|
||||||
|
.markdown-content a {
|
||||||
|
color: var(--brand-3);
|
||||||
|
text-decoration: underline;
|
||||||
|
text-underline-offset: 0.15em;
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .markdown-content a {
|
||||||
|
color: var(--speaking);
|
||||||
|
}
|
||||||
|
.markdown-content :is(a, pre, .markdown-table):focus-visible {
|
||||||
|
outline: 2px solid var(--brand);
|
||||||
|
outline-offset: 3px;
|
||||||
|
}
|
||||||
|
.markdown-content blockquote {
|
||||||
|
margin: 0.75em 0;
|
||||||
|
padding-inline-start: 12px;
|
||||||
|
border-inline-start: 3px solid var(--brand);
|
||||||
|
color: var(--text-dim);
|
||||||
|
}
|
||||||
|
.markdown-content code {
|
||||||
|
padding: 0.15em 0.35em;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: color-mix(in srgb, currentColor 8%, transparent);
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
.markdown-content pre {
|
||||||
|
max-width: 100%;
|
||||||
|
margin: 0.75em 0;
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid var(--border-strong);
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: auto;
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
.markdown-content pre code {
|
||||||
|
display: block;
|
||||||
|
padding: 0;
|
||||||
|
background: transparent;
|
||||||
|
white-space: pre;
|
||||||
|
overflow-wrap: normal;
|
||||||
|
word-break: normal;
|
||||||
|
}
|
||||||
|
.markdown-content hr {
|
||||||
|
margin: 1em 0;
|
||||||
|
border: 0;
|
||||||
|
border-top: 1px solid var(--border-strong);
|
||||||
|
}
|
||||||
|
.markdown-content img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
.markdown-content .markdown-table {
|
||||||
|
max-width: 100%;
|
||||||
|
margin: 0.75em 0;
|
||||||
|
overflow-x: auto;
|
||||||
|
overscroll-behavior-x: contain;
|
||||||
|
}
|
||||||
|
.markdown-content table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 0.92em;
|
||||||
|
}
|
||||||
|
.markdown-content :is(th, td) {
|
||||||
|
padding: 7px 10px;
|
||||||
|
border: 1px solid var(--border-strong);
|
||||||
|
text-align: start;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
.markdown-content th {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.markdown-content [align="center"] {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.markdown-content [align="right"] {
|
||||||
|
text-align: end;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/* Native range pseudo-elements cannot share a selector across browser engines. */
|
||||||
|
.gate-range::-webkit-slider-runnable-track {
|
||||||
|
height: 14px;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
.gate-range::-moz-range-track {
|
||||||
|
height: 14px;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
.gate-range::-webkit-slider-thumb {
|
||||||
|
appearance: none;
|
||||||
|
width: 6px;
|
||||||
|
height: 18px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: var(--text);
|
||||||
|
border: 2px solid var(--bg-elev);
|
||||||
|
box-shadow: 0 0 0 1px var(--border-strong);
|
||||||
|
}
|
||||||
|
.gate-range::-moz-range-thumb {
|
||||||
|
width: 6px;
|
||||||
|
height: 18px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: var(--text);
|
||||||
|
border: 2px solid var(--bg-elev);
|
||||||
|
box-shadow: 0 0 0 1px var(--border-strong);
|
||||||
|
}
|
||||||
@@ -26,9 +26,9 @@
|
|||||||
--color-voice-user: var(--voice-user);
|
--color-voice-user: var(--voice-user);
|
||||||
--color-voice-assistant: var(--voice-assistant);
|
--color-voice-assistant: var(--voice-assistant);
|
||||||
--color-voice-tool: var(--voice-tool);
|
--color-voice-tool: var(--voice-tool);
|
||||||
--radius-control: var(--radius-sm);
|
--radius-control: var(--app-radius-control);
|
||||||
--radius-panel: var(--radius-md);
|
--radius-panel: var(--app-radius-panel);
|
||||||
--radius-card: var(--radius-lg);
|
--radius-card: var(--app-radius-card);
|
||||||
--shadow-panel: var(--shadow-soft);
|
--shadow-panel: var(--shadow-soft);
|
||||||
--shadow-brand: var(--shadow-glow-brand);
|
--shadow-brand: var(--shadow-glow-brand);
|
||||||
--shadow-card-glass: var(--shadow-glass);
|
--shadow-card-glass: var(--shadow-glass);
|
||||||
@@ -73,17 +73,10 @@
|
|||||||
--voice-user: var(--accent-2);
|
--voice-user: var(--accent-2);
|
||||||
--voice-assistant: var(--accent);
|
--voice-assistant: var(--accent);
|
||||||
--voice-tool: var(--processing);
|
--voice-tool: var(--processing);
|
||||||
--audio-level: 0;
|
|
||||||
--bar0: 0;
|
|
||||||
--bar1: 0;
|
|
||||||
--bar2: 0;
|
|
||||||
--bar3: 0;
|
|
||||||
--bar4: 0;
|
|
||||||
|
|
||||||
--radius-sm: 10px;
|
--app-radius-control: 10px;
|
||||||
--radius-md: 16px;
|
--app-radius-panel: 16px;
|
||||||
--radius-lg: 26px;
|
--app-radius-card: 26px;
|
||||||
--radius-xl: 34px;
|
|
||||||
|
|
||||||
--shadow-soft: 0 10px 40px rgba(0, 0, 0, 0.35);
|
--shadow-soft: 0 10px 40px rgba(0, 0, 0, 0.35);
|
||||||
--shadow-glow-brand:
|
--shadow-glow-brand:
|
||||||
@@ -161,9 +154,8 @@
|
|||||||
--message-user-shadow: none;
|
--message-user-shadow: none;
|
||||||
--message-assistant-shadow: none;
|
--message-assistant-shadow: none;
|
||||||
|
|
||||||
--radius-sm: 10px;
|
--app-radius-control: 10px;
|
||||||
--radius-md: 16px;
|
--app-radius-panel: 16px;
|
||||||
--radius-lg: 22px;
|
--app-radius-card: 22px;
|
||||||
--radius-xl: 28px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,986 @@
|
|||||||
|
/* Audio-reactive artwork. Runtime CSS variables are written by the audio engine, outside React. */
|
||||||
|
:root {
|
||||||
|
--audio-level: 0;
|
||||||
|
--ai-audio-level: 0;
|
||||||
|
--bar0: 0;
|
||||||
|
--bar1: 0;
|
||||||
|
--bar2: 0;
|
||||||
|
--bar3: 0;
|
||||||
|
--bar4: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stage::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 46%;
|
||||||
|
left: 50%;
|
||||||
|
width: clamp(260px, 58vw, 620px);
|
||||||
|
height: clamp(260px, 58vw, 620px);
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
background: radial-gradient(
|
||||||
|
circle,
|
||||||
|
color-mix(in srgb, var(--brand) 22%, transparent) 0%,
|
||||||
|
color-mix(in srgb, var(--brand-2) 12%, transparent) 45%,
|
||||||
|
transparent 75%
|
||||||
|
);
|
||||||
|
filter: blur(12px);
|
||||||
|
opacity: 0.65;
|
||||||
|
pointer-events: none;
|
||||||
|
border-radius: 999px;
|
||||||
|
animation: pedestalBreathe 8s ease-in-out infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle {
|
||||||
|
position: relative;
|
||||||
|
width: clamp(120px, min(38svw, 42svh), 300px);
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
padding: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
outline: none;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
color: var(--glow, var(--accent));
|
||||||
|
transition:
|
||||||
|
transform 0.18s ease,
|
||||||
|
filter 0.18s ease;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
touch-action: manipulation;
|
||||||
|
}
|
||||||
|
.circle::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: -8%;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: conic-gradient(
|
||||||
|
from 0deg,
|
||||||
|
color-mix(in srgb, var(--brand) 65%, transparent) 0%,
|
||||||
|
color-mix(in srgb, var(--brand-2) 55%, transparent) 35%,
|
||||||
|
color-mix(in srgb, var(--brand-3) 55%, transparent) 65%,
|
||||||
|
color-mix(in srgb, var(--brand) 65%, transparent) 100%
|
||||||
|
);
|
||||||
|
filter: blur(14px);
|
||||||
|
opacity: 0.55;
|
||||||
|
z-index: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
animation: orbConicSpin 18s linear infinite;
|
||||||
|
}
|
||||||
|
.circle:hover {
|
||||||
|
filter: brightness(1.08);
|
||||||
|
}
|
||||||
|
.circle:hover::before {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
.circle:active {
|
||||||
|
transform: scale(0.97);
|
||||||
|
filter: brightness(0.92);
|
||||||
|
}
|
||||||
|
.circle:focus-visible .circle-core {
|
||||||
|
outline: 2px solid var(--glow, var(--accent));
|
||||||
|
outline-offset: 6px;
|
||||||
|
}
|
||||||
|
.circle[disabled] {
|
||||||
|
cursor: default;
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle-glow {
|
||||||
|
position: absolute;
|
||||||
|
inset: -8%;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: radial-gradient(circle at center, var(--glow, var(--accent)) 0%, transparent 60%);
|
||||||
|
filter: blur(34px);
|
||||||
|
opacity: 0.55;
|
||||||
|
transform: scale(1);
|
||||||
|
transition:
|
||||||
|
opacity 0.25s,
|
||||||
|
background 0.25s,
|
||||||
|
transform 1.4s ease-in-out;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
.circle.state-idle .circle-glow,
|
||||||
|
.circle.state-idle::before {
|
||||||
|
animation:
|
||||||
|
orbConicSpin 22s linear infinite,
|
||||||
|
orbBreatheGlow 5s ease-in-out infinite alternate;
|
||||||
|
}
|
||||||
|
.circle-ring,
|
||||||
|
.circle-ring-outer {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
border-radius: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
pointer-events: none;
|
||||||
|
transition:
|
||||||
|
opacity 0.4s ease,
|
||||||
|
border-color 0.4s ease,
|
||||||
|
transform 0.3s ease;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.circle-ring {
|
||||||
|
width: 86%;
|
||||||
|
height: 86%;
|
||||||
|
border: 1.5px solid transparent;
|
||||||
|
background:
|
||||||
|
linear-gradient(var(--bg, #07080d), var(--bg, #07080d)) padding-box,
|
||||||
|
conic-gradient(
|
||||||
|
from 0deg,
|
||||||
|
color-mix(in srgb, var(--brand) 80%, transparent),
|
||||||
|
color-mix(in srgb, var(--brand-2) 70%, transparent) 40%,
|
||||||
|
color-mix(in srgb, var(--brand-3) 75%, transparent) 70%,
|
||||||
|
color-mix(in srgb, var(--brand) 80%, transparent)
|
||||||
|
)
|
||||||
|
border-box;
|
||||||
|
border-color: transparent;
|
||||||
|
mask:
|
||||||
|
linear-gradient(#000, #000) content-box,
|
||||||
|
linear-gradient(#000, #000);
|
||||||
|
-webkit-mask:
|
||||||
|
linear-gradient(#000, #000) content-box,
|
||||||
|
linear-gradient(#000, #000);
|
||||||
|
-webkit-mask-composite: xor;
|
||||||
|
mask-composite: exclude;
|
||||||
|
box-shadow: 0 0 20px -6px color-mix(in srgb, var(--brand) 55%, transparent);
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
.circle-ring-outer {
|
||||||
|
width: 98%;
|
||||||
|
height: 98%;
|
||||||
|
border: 1px dashed color-mix(in srgb, var(--glow, var(--accent)) 30%, transparent);
|
||||||
|
opacity: 0.55;
|
||||||
|
}
|
||||||
|
.circle.state-idle .circle-ring-outer {
|
||||||
|
animation: outerRingPulse 6s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle-core {
|
||||||
|
position: relative;
|
||||||
|
width: 72%;
|
||||||
|
height: 72%;
|
||||||
|
border-radius: 50%;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 30% 22%, rgba(255, 255, 255, 0.18) 0%, rgba(255, 255, 255, 0.04) 20%, transparent 45%),
|
||||||
|
radial-gradient(
|
||||||
|
circle at 35% 28%,
|
||||||
|
color-mix(in srgb, var(--glow, var(--accent)) 32%, transparent),
|
||||||
|
color-mix(in srgb, var(--glow, var(--accent)) 12%, transparent) 55%,
|
||||||
|
color-mix(in srgb, var(--glow, var(--accent)) 5%, transparent)
|
||||||
|
);
|
||||||
|
border: 1.5px solid color-mix(in srgb, var(--glow, var(--accent)) 48%, transparent);
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
box-shadow:
|
||||||
|
0 0 44px -8px color-mix(in srgb, var(--glow, var(--accent)) 42%, transparent),
|
||||||
|
inset 0 0 34px color-mix(in srgb, var(--glow, var(--accent)) 22%, transparent),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.12);
|
||||||
|
transition:
|
||||||
|
background 0.4s ease,
|
||||||
|
border-color 0.4s ease,
|
||||||
|
box-shadow 0.4s ease,
|
||||||
|
transform 0.4s ease;
|
||||||
|
z-index: 2;
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
-webkit-backdrop-filter: blur(8px);
|
||||||
|
}
|
||||||
|
.circle.state-idle .circle-core {
|
||||||
|
animation: coreBreathe 5s ease-in-out infinite alternate;
|
||||||
|
}
|
||||||
|
.circle-indicator {
|
||||||
|
position: relative;
|
||||||
|
width: 44%;
|
||||||
|
height: 44%;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
color: var(--glow, var(--accent));
|
||||||
|
}
|
||||||
|
.circle-indicator > .ind {
|
||||||
|
grid-area: 1 / 1;
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.85);
|
||||||
|
transition:
|
||||||
|
opacity 0.25s ease,
|
||||||
|
transform 0.25s ease;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.circle-indicator > svg.ind {
|
||||||
|
width: 60%;
|
||||||
|
height: 60%;
|
||||||
|
}
|
||||||
|
.ind-spinner {
|
||||||
|
width: 48%;
|
||||||
|
height: 48%;
|
||||||
|
border: 3px solid currentColor;
|
||||||
|
border-right-color: transparent;
|
||||||
|
border-radius: 50%;
|
||||||
|
opacity: 0;
|
||||||
|
animation: ind-spin 0.9s linear infinite;
|
||||||
|
}
|
||||||
|
.ind-thinking {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
width: 60%;
|
||||||
|
height: 60%;
|
||||||
|
}
|
||||||
|
.ind-thinking .dot {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: currentColor;
|
||||||
|
opacity: 0.3;
|
||||||
|
animation: thinking-dot 1.25s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
.ind-thinking .dot:nth-child(1) {
|
||||||
|
animation-delay: 0s;
|
||||||
|
}
|
||||||
|
.ind-thinking .dot:nth-child(2) {
|
||||||
|
animation-delay: 0.16s;
|
||||||
|
}
|
||||||
|
.ind-thinking .dot:nth-child(3) {
|
||||||
|
animation-delay: 0.32s;
|
||||||
|
}
|
||||||
|
.ind-bars {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
height: 42%;
|
||||||
|
}
|
||||||
|
.ind-bars .bar {
|
||||||
|
width: 4px;
|
||||||
|
min-height: 4px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: currentColor;
|
||||||
|
opacity: 0.7;
|
||||||
|
--h: var(--bar0, 0);
|
||||||
|
height: calc(4px + var(--h) * 36px);
|
||||||
|
transition:
|
||||||
|
height 0.08s ease-out,
|
||||||
|
opacity 0.08s ease-out;
|
||||||
|
}
|
||||||
|
.ind-bars .bar:nth-child(1) {
|
||||||
|
--h: var(--bar0);
|
||||||
|
}
|
||||||
|
.ind-bars .bar:nth-child(2) {
|
||||||
|
--h: var(--bar1);
|
||||||
|
}
|
||||||
|
.ind-bars .bar:nth-child(3) {
|
||||||
|
--h: var(--bar2);
|
||||||
|
}
|
||||||
|
.ind-bars .bar:nth-child(4) {
|
||||||
|
--h: var(--bar3);
|
||||||
|
}
|
||||||
|
.ind-bars .bar:nth-child(5) {
|
||||||
|
--h: var(--bar4);
|
||||||
|
}
|
||||||
|
.ind-bars .bar {
|
||||||
|
opacity: calc(0.55 + 0.45 * var(--h));
|
||||||
|
}
|
||||||
|
.state-connecting .ind-spinner,
|
||||||
|
.state-queued .ind-spinner,
|
||||||
|
.state-processing .ind-thinking,
|
||||||
|
.state-listening .ind-bars,
|
||||||
|
.state-user-speaking .ind-bars,
|
||||||
|
.state-ai-speaking .ind-voice,
|
||||||
|
.state-error .ind-error {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
.ind-mic {
|
||||||
|
color: color-mix(in srgb, var(--glow, var(--accent)) 85%, white);
|
||||||
|
}
|
||||||
|
.circle-caption {
|
||||||
|
margin: 0;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.24em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--text-faint);
|
||||||
|
min-height: 1.3em;
|
||||||
|
text-align: center;
|
||||||
|
opacity: 0.78;
|
||||||
|
transition:
|
||||||
|
opacity 0.25s ease,
|
||||||
|
color 0.25s ease,
|
||||||
|
transform 0.25s ease,
|
||||||
|
min-height 0.25s ease,
|
||||||
|
background 0.3s ease,
|
||||||
|
filter 0.3s ease;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
.circle-caption:empty::before {
|
||||||
|
content: "\00a0";
|
||||||
|
}
|
||||||
|
.stage:has(.circle.state-idle) .circle-caption {
|
||||||
|
background: var(--brand-gradient);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
color: transparent;
|
||||||
|
opacity: 1;
|
||||||
|
filter: drop-shadow(0 2px 10px color-mix(in srgb, var(--brand) 40%, transparent));
|
||||||
|
animation: idleCaptionBob 3.6s ease-in-out infinite alternate;
|
||||||
|
}
|
||||||
|
.circle-subcaption {
|
||||||
|
margin: 8px 0 0;
|
||||||
|
max-width: 32ch;
|
||||||
|
font-size: 13.5px;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: var(--text-dim);
|
||||||
|
text-align: center;
|
||||||
|
text-wrap: balance;
|
||||||
|
transition: opacity 0.25s ease;
|
||||||
|
}
|
||||||
|
.mic-gate {
|
||||||
|
position: relative;
|
||||||
|
flex: none;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.mic-gate-arc {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.25s ease;
|
||||||
|
}
|
||||||
|
.orb-wrap.live .mic-gate-arc {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.mga-track {
|
||||||
|
stroke: rgba(255, 255, 255, 0.1);
|
||||||
|
stroke-width: 1.5;
|
||||||
|
stroke-linecap: round;
|
||||||
|
}
|
||||||
|
.mga-fill {
|
||||||
|
stroke: var(--accent-2);
|
||||||
|
stroke-width: 2;
|
||||||
|
stroke-linecap: round;
|
||||||
|
opacity: 0.85;
|
||||||
|
transition: stroke-dashoffset 0.06s linear;
|
||||||
|
}
|
||||||
|
.mga-handle {
|
||||||
|
fill: var(--text);
|
||||||
|
stroke: none;
|
||||||
|
transition:
|
||||||
|
fill 0.2s ease,
|
||||||
|
filter 0.2s ease;
|
||||||
|
}
|
||||||
|
.mic-gate.gate-open .mga-handle {
|
||||||
|
fill: var(--accent-2);
|
||||||
|
filter: drop-shadow(0 0 3px var(--accent-2));
|
||||||
|
}
|
||||||
|
.mga-hit {
|
||||||
|
stroke: transparent;
|
||||||
|
stroke-width: 16;
|
||||||
|
pointer-events: none;
|
||||||
|
cursor: pointer;
|
||||||
|
touch-action: none;
|
||||||
|
}
|
||||||
|
.orb-wrap.live .mga-hit {
|
||||||
|
pointer-events: stroke;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-btn {
|
||||||
|
flex: none;
|
||||||
|
width: 52px;
|
||||||
|
height: 52px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid var(--border-strong);
|
||||||
|
background: color-mix(in srgb, var(--bg-elev-2) 80%, #2a2e3c);
|
||||||
|
color: var(--text);
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
touch-action: manipulation;
|
||||||
|
box-shadow:
|
||||||
|
0 6px 16px rgba(0, 0, 0, 0.35),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.06);
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.55);
|
||||||
|
width: 0;
|
||||||
|
padding: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
overflow: hidden;
|
||||||
|
transition:
|
||||||
|
opacity 0.25s ease,
|
||||||
|
transform 0.25s ease,
|
||||||
|
width 0.25s ease,
|
||||||
|
background 0.15s,
|
||||||
|
color 0.15s,
|
||||||
|
border-color 0.15s;
|
||||||
|
}
|
||||||
|
.side-btn:hover {
|
||||||
|
background: color-mix(in srgb, var(--bg-elev-2) 60%, #323746);
|
||||||
|
border-color: var(--text-dim);
|
||||||
|
}
|
||||||
|
.side-btn svg {
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
.side-btn.muted {
|
||||||
|
color: #fff;
|
||||||
|
border-color: var(--error);
|
||||||
|
background: color-mix(in srgb, var(--error) 70%, #1a0e13);
|
||||||
|
}
|
||||||
|
.orb-wrap.live .side-btn {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
width: 52px;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
.circle.state-connecting {
|
||||||
|
--glow: #facc15;
|
||||||
|
}
|
||||||
|
.circle.state-queued {
|
||||||
|
--glow: #94a3b8;
|
||||||
|
}
|
||||||
|
.circle.state-queued .ind-spinner {
|
||||||
|
animation-duration: 2.4s;
|
||||||
|
}
|
||||||
|
.circle.state-queued .circle-ring {
|
||||||
|
animation: breathe 2.6s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
.circle.state-your-turn {
|
||||||
|
--glow: var(--accent);
|
||||||
|
}
|
||||||
|
.circle.state-your-turn .circle-ring {
|
||||||
|
animation: breathe 1.6s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
.circle.state-listening,
|
||||||
|
.circle.state-user-speaking {
|
||||||
|
--glow: var(--listening);
|
||||||
|
}
|
||||||
|
.circle.state-processing {
|
||||||
|
--glow: var(--processing);
|
||||||
|
}
|
||||||
|
.circle.state-ai-speaking {
|
||||||
|
--glow: var(--speaking);
|
||||||
|
}
|
||||||
|
.circle.state-error {
|
||||||
|
--glow: var(--error);
|
||||||
|
}
|
||||||
|
.circle.state-connecting .circle-core {
|
||||||
|
animation: thinking 1.4s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
.circle.state-user-speaking .circle-ring {
|
||||||
|
animation: none;
|
||||||
|
opacity: calc(0.25 + 0.55 * var(--audio-level));
|
||||||
|
transform: translate(-50%, -50%) scale(calc(1 + 0.08 * var(--audio-level)));
|
||||||
|
transition:
|
||||||
|
transform 0.08s linear,
|
||||||
|
opacity 0.08s linear;
|
||||||
|
}
|
||||||
|
.circle.state-user-speaking .circle-ring-outer {
|
||||||
|
opacity: calc(0.1 + 0.35 * var(--audio-level));
|
||||||
|
transform: translate(-50%, -50%) scale(calc(1 + 0.12 * var(--audio-level)));
|
||||||
|
transition:
|
||||||
|
transform 0.08s linear,
|
||||||
|
opacity 0.08s linear;
|
||||||
|
}
|
||||||
|
.circle.state-listening .circle-ring {
|
||||||
|
animation: breathe 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
.circle.state-ai-speaking .circle-core {
|
||||||
|
animation: none;
|
||||||
|
transform: scale(calc(1 + 0.09 * var(--ai-audio-level, 0)));
|
||||||
|
transition: transform 0.08s linear;
|
||||||
|
}
|
||||||
|
.circle.state-ai-speaking .circle-ring {
|
||||||
|
animation: none;
|
||||||
|
opacity: calc(0.3 + 0.5 * var(--ai-audio-level, 0));
|
||||||
|
transform: translate(-50%, -50%) scale(calc(1 + 0.05 * var(--ai-audio-level, 0)));
|
||||||
|
transition:
|
||||||
|
transform 0.08s linear,
|
||||||
|
opacity 0.08s linear;
|
||||||
|
}
|
||||||
|
.circle.state-ai-speaking .circle-ring-outer {
|
||||||
|
animation: none;
|
||||||
|
opacity: calc(0.15 + 0.55 * var(--ai-audio-level, 0));
|
||||||
|
transform: translate(-50%, -50%) scale(calc(1 + 0.18 * var(--ai-audio-level, 0)));
|
||||||
|
transition:
|
||||||
|
transform 0.08s linear,
|
||||||
|
opacity 0.08s linear;
|
||||||
|
}
|
||||||
|
.circle.state-ai-speaking .circle-glow {
|
||||||
|
opacity: calc(0.35 + 0.45 * var(--ai-audio-level, 0));
|
||||||
|
transition: opacity 0.08s linear;
|
||||||
|
}
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.circle {
|
||||||
|
width: clamp(110px, min(56svw, 36svh), 220px);
|
||||||
|
}
|
||||||
|
.side-btn {
|
||||||
|
width: 44px;
|
||||||
|
height: 0;
|
||||||
|
transition:
|
||||||
|
opacity 0.25s ease,
|
||||||
|
transform 0.25s ease,
|
||||||
|
height 0.25s ease,
|
||||||
|
background 0.15s,
|
||||||
|
color 0.15s,
|
||||||
|
border-color 0.15s;
|
||||||
|
}
|
||||||
|
.side-btn svg {
|
||||||
|
width: 19px;
|
||||||
|
height: 19px;
|
||||||
|
}
|
||||||
|
.orb-wrap.live .side-btn {
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
}
|
||||||
|
.mic-gate-arc {
|
||||||
|
width: 70px;
|
||||||
|
height: 70px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (min-width: 601px) {
|
||||||
|
.circle-caption {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-height: 780px) {
|
||||||
|
.circle {
|
||||||
|
width: clamp(130px, min(36svw, 38svh), 280px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-height: 560px) {
|
||||||
|
.circle {
|
||||||
|
width: clamp(90px, min(40svw, 34svh), 200px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-mic-circle {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
width: 116px;
|
||||||
|
height: 116px;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #ffffff;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: visible;
|
||||||
|
flex: none;
|
||||||
|
transition:
|
||||||
|
transform 180ms cubic-bezier(0.2, 0.7, 0.2, 1),
|
||||||
|
box-shadow 180ms ease;
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-mic-circle .wmc-halo {
|
||||||
|
position: absolute;
|
||||||
|
inset: -22px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: radial-gradient(closest-side, rgba(59, 130, 246, 0.22), rgba(59, 130, 246, 0.1) 55%, transparent 75%);
|
||||||
|
z-index: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
animation: wmcHaloPulse 3.2s ease-in-out infinite alternate;
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-mic-circle .wmc-ring {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 4px;
|
||||||
|
background: linear-gradient(135deg, #2563eb 0%, #3b82f6 45%, #60a5fa 100%);
|
||||||
|
z-index: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-mic-circle .wmc-ring::before {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-mic-circle .wmc-core {
|
||||||
|
position: absolute;
|
||||||
|
inset: 4px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: linear-gradient(135deg, #2563eb 0%, #3b82f6 55%, #60a5fa 100%);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.35),
|
||||||
|
inset 0 -3px 6px rgba(15, 23, 42, 0.12),
|
||||||
|
0 8px 22px rgba(37, 99, 235, 0.22);
|
||||||
|
z-index: 1;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-mic-circle .wmc-icon {
|
||||||
|
position: relative;
|
||||||
|
z-index: 5;
|
||||||
|
color: #ffffff;
|
||||||
|
fill: #ffffff;
|
||||||
|
stroke: #ffffff;
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
flex: none;
|
||||||
|
display: none;
|
||||||
|
filter: drop-shadow(0 2px 4px rgba(15, 23, 42, 0.14));
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-mic-circle.state-idle > svg.wmc-icon.wmc-mic,
|
||||||
|
html[data-theme="white"] .white-mic-circle.state-idle .wmc-icon.wmc-mic,
|
||||||
|
html[data-theme="white"] .white-mic-circle.state-listening .wmc-icon.wmc-mic,
|
||||||
|
html[data-theme="white"] .white-mic-circle.state-user-speaking .wmc-icon.wmc-mic,
|
||||||
|
html[data-theme="white"] .white-mic-circle.state-ai-speaking .wmc-icon.wmc-mic {
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-mic-circle.state-your-turn > svg.wmc-icon.wmc-mic,
|
||||||
|
html[data-theme="white"] .white-mic-circle.state-your-turn .wmc-icon.wmc-mic {
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-mic-circle.state-error > svg.wmc-icon.wmc-mic,
|
||||||
|
html[data-theme="white"] .white-mic-circle.state-error .wmc-icon.wmc-mic {
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-mic-circle.state-processing > .wmc-icon.wmc-spinner,
|
||||||
|
html[data-theme="white"] .white-mic-circle.state-processing .wmc-icon.wmc-spinner,
|
||||||
|
html[data-theme="white"] .white-mic-circle.state-connecting > .wmc-icon.wmc-spinner,
|
||||||
|
html[data-theme="white"] .white-mic-circle.state-connecting .wmc-icon.wmc-spinner,
|
||||||
|
html[data-theme="white"] .white-mic-circle.state-queued > .wmc-icon.wmc-spinner,
|
||||||
|
html[data-theme="white"] .white-mic-circle.state-queued .wmc-icon.wmc-spinner {
|
||||||
|
display: block;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 4px solid rgba(255, 255, 255, 0.22);
|
||||||
|
border-top-color: #ffffff;
|
||||||
|
animation: voice-spinner-spin 850ms linear infinite;
|
||||||
|
margin: 4px;
|
||||||
|
padding: 0;
|
||||||
|
background: transparent;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-mic-circle:disabled {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-mic-circle:focus-visible {
|
||||||
|
outline: 3px solid #1d4ed8;
|
||||||
|
outline-offset: 4px;
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-mic-circle.state-your-turn .wmc-ring {
|
||||||
|
animation: wmcListeningPulse 1.6s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-waveform {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(22px + 116px / 2);
|
||||||
|
transform: translateY(-50%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 7px;
|
||||||
|
height: 70px;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-waveform-left {
|
||||||
|
left: calc(50% - 58px - 90px);
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-waveform-right {
|
||||||
|
right: calc(50% - 58px - 90px);
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-waveform .w-bar {
|
||||||
|
display: block;
|
||||||
|
width: 6px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: linear-gradient(180deg, #3b82f6, #60a5fa);
|
||||||
|
opacity: 0.8;
|
||||||
|
transform-origin: center;
|
||||||
|
height: calc(var(--bar-h, 0.5) * 70px);
|
||||||
|
min-height: 8px;
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-mic-circle.state-your-turn ~ * .w-bar,
|
||||||
|
html[data-theme="white"] .white-mic-stage:has(.state-your-turn) .w-bar {
|
||||||
|
animation: wBarDance 900ms ease-in-out infinite alternate;
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-waveform .wb-1 {
|
||||||
|
animation-delay: -0.6s;
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-waveform .wb-2 {
|
||||||
|
animation-delay: -0.5s;
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-waveform .wb-3 {
|
||||||
|
animation-delay: -0.4s;
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-waveform .wb-4 {
|
||||||
|
animation-delay: -0.3s;
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-waveform .wb-5 {
|
||||||
|
animation-delay: -0.2s;
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-waveform .wb-6 {
|
||||||
|
animation-delay: -0.1s;
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-waveform .wb-7 {
|
||||||
|
animation-delay: 0s;
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-waveform-right .wb-1 {
|
||||||
|
animation-delay: -0s;
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-waveform-right .wb-2 {
|
||||||
|
animation-delay: -0.1s;
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-waveform-right .wb-3 {
|
||||||
|
animation-delay: -0.2s;
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-waveform-right .wb-4 {
|
||||||
|
animation-delay: -0.3s;
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-waveform-right .wb-5 {
|
||||||
|
animation-delay: -0.4s;
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-waveform-right .wb-6 {
|
||||||
|
animation-delay: -0.5s;
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-waveform-right .wb-7 {
|
||||||
|
animation-delay: -0.6s;
|
||||||
|
}
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
html[data-theme="white"] .white-waveform-left {
|
||||||
|
left: calc(50% - 58px - 72px);
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-waveform-right {
|
||||||
|
right: calc(50% - 58px - 72px);
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-waveform {
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-waveform .w-bar {
|
||||||
|
width: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-height: 640px) {
|
||||||
|
html[data-theme="white"] .white-mic-circle {
|
||||||
|
width: 98px;
|
||||||
|
height: 98px;
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-mic-circle .wmc-icon {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-mic-circle.state-processing .wmc-spinner,
|
||||||
|
html[data-theme="white"] .white-mic-circle.state-connecting .wmc-spinner,
|
||||||
|
html[data-theme="white"] .white-mic-circle.state-queued .wmc-spinner {
|
||||||
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-waveform .w-bar {
|
||||||
|
height: calc(var(--bar-h, 0.5) * 56px);
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-waveform {
|
||||||
|
height: 56px;
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-waveform-left {
|
||||||
|
left: calc(50% - 49px - 76px);
|
||||||
|
top: calc(14px + 49px);
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-waveform-right {
|
||||||
|
right: calc(50% - 49px - 76px);
|
||||||
|
top: calc(14px + 49px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.circle-caption {
|
||||||
|
color: var(--text-dim);
|
||||||
|
}
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.circle {
|
||||||
|
width: clamp(110px, 32vw, 176px);
|
||||||
|
}
|
||||||
|
.orb-wrap.live .side-btn {
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
}
|
||||||
|
.mic-gate-arc {
|
||||||
|
width: 62px;
|
||||||
|
height: 62px;
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-waveform {
|
||||||
|
top: 74px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-height: 640px) {
|
||||||
|
html[data-theme="white"] .white-waveform {
|
||||||
|
top: 57px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.state-idle .ind-mic {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
html[data-theme="white"] .white-mic-circle svg.wmc-mic {
|
||||||
|
fill: none;
|
||||||
|
}
|
||||||
|
.mic-gate-arc:focus-visible {
|
||||||
|
outline: 2px solid var(--accent-2);
|
||||||
|
outline-offset: 4px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
@keyframes voice-spinner-spin {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes pedestalBreathe {
|
||||||
|
0% {
|
||||||
|
transform: translate(-50%, -50%) scale(1);
|
||||||
|
opacity: 0.55;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translate(-50%, -50%) scale(1.06);
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translate(-50%, -50%) scale(0.98);
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes orbConicSpin {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes orbBreatheGlow {
|
||||||
|
0% {
|
||||||
|
opacity: 0.42;
|
||||||
|
transform: scale(0.985);
|
||||||
|
filter: blur(30px);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.72;
|
||||||
|
transform: scale(1.04);
|
||||||
|
filter: blur(40px);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0.48;
|
||||||
|
transform: scale(1);
|
||||||
|
filter: blur(32px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes outerRingPulse {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 0.4;
|
||||||
|
transform: translate(-50%, -50%) scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.65;
|
||||||
|
transform: translate(-50%, -50%) scale(1.02);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes coreBreathe {
|
||||||
|
0% {
|
||||||
|
transform: scale(0.992);
|
||||||
|
box-shadow:
|
||||||
|
0 0 36px -8px color-mix(in srgb, var(--brand) 40%, transparent),
|
||||||
|
inset 0 0 26px color-mix(in srgb, var(--brand) 16%, transparent);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(1.01);
|
||||||
|
box-shadow:
|
||||||
|
0 0 54px -4px color-mix(in srgb, var(--brand-2) 48%, transparent),
|
||||||
|
inset 0 0 38px color-mix(in srgb, var(--brand-2) 22%, transparent);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
box-shadow:
|
||||||
|
0 0 44px -6px color-mix(in srgb, var(--brand-3) 42%, transparent),
|
||||||
|
inset 0 0 32px color-mix(in srgb, var(--brand-3) 20%, transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes idleCaptionBob {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: translateY(0);
|
||||||
|
letter-spacing: 0.24em;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
letter-spacing: 0.26em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes breathe {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: translate(-50%, -50%) scale(1);
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translate(-50%, -50%) scale(1.06);
|
||||||
|
opacity: 0.15;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes thinking {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(0.96);
|
||||||
|
opacity: 0.45;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes thinking-dot {
|
||||||
|
0%,
|
||||||
|
60%,
|
||||||
|
100% {
|
||||||
|
transform: scale(0.7);
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
30% {
|
||||||
|
transform: scale(1.15);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes ind-spin {
|
||||||
|
from {
|
||||||
|
transform: scale(1) rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: scale(1) rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes wmcHaloPulse {
|
||||||
|
0% {
|
||||||
|
transform: scale(0.98);
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1.08);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes wmcListeningPulse {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.45);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
box-shadow: 0 0 0 18px rgba(59, 130, 246, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes wBarDance {
|
||||||
|
from {
|
||||||
|
transform: scaleY(0.55);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: scaleY(1.05);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user