feat(voice): reconcile preview and settings with main

add the shared Voice Preview and Settings panel with browser-local preview
and inactivity preferences, keyboard controls and responsive positioning

preserve main’s Rotation and Context behavior, clarify panel routing and
refresh schema pins within the unchanged prompt limit
This commit is contained in:
manjunath committed 2026-10-24 15:20:15 +05:30
1 parent 9d3548c27c
commit 5c3b66c263
39 files changed
+5274 -111

No files matched your search

+246
View File
@@ -387,3 +387,249 @@ body.recording-mode .gev-pointer-reticle {
animation: none;
}
}
/* Opt-in shared panel: header stays fixed while only the body scrolls. */
#gev-voice-control .gev-voice-panel {
position: absolute;
bottom: calc(100% + 0.7rem);
left: 50%;
z-index: 10;
display: flex;
flex-direction: column;
width: clamp(18rem, 26vw, 22rem);
max-width: calc(100vw - 1.5rem);
max-height: min(
28rem,
max(7.5rem, calc(100dvh - 12rem - var(--gev-provider-lift, 0px)))
);
box-sizing: border-box;
padding: 0.6rem 0.7rem 0.65rem;
color: rgba(223, 245, 250, 0.92);
background: rgba(7, 22, 31, 0.96);
border: 1px solid rgba(54, 220, 255, 0.34);
border-radius: 0.6rem;
box-shadow:
0 0.85rem 2rem rgba(0, 0, 0, 0.58),
0 0 1.2rem rgba(0, 212, 255, 0.08);
backdrop-filter: blur(24px) saturate(1.3);
-webkit-backdrop-filter: blur(24px) saturate(1.3);
color-scheme: dark;
font: 0.75rem/1.45 var(--font-sans);
text-align: left;
transform: translateX(-50%);
}
:root[data-ui-theme='cyber'] #gev-voice-control .gev-voice-panel {
background-color: var(--cyber-panel-surface);
background-image: var(--cyber-panel-surface-detail);
border-color: var(--glass-border);
box-shadow: var(--cyber-panel-shadow);
}
#gev-voice-control .gev-voice-panel[hidden],
#gev-voice-control .gev-voice-panel [hidden] {
display: none;
}
#gev-voice-control .gev-voice-panel-header {
display: flex;
flex: 0 0 auto;
align-items: center;
justify-content: space-between;
gap: 0.35rem;
padding: 0 0 0.5rem;
border-bottom: 1px solid var(--glass-border);
}
.gev-voice-panel-title {
color: var(--voice-panel-accent, var(--accent));
font: 600 0.65rem/1 var(--font-mono);
letter-spacing: 0.14em;
}
.gev-voice-panel-tabs {
margin-left: auto;
}
#gev-voice-control .gev-voice-panel-header .gev-voice-panel-tab {
display: inline-flex;
align-items: center;
justify-content: center;
width: 2rem;
padding: 0;
}
#gev-voice-control .gev-voice-panel-tab-icon {
font-size: 18px;
}
.gev-voice-panel-header [role='tablist'] {
display: flex;
flex-wrap: wrap;
gap: 0.2rem;
min-width: 0;
}
#gev-voice-control
.gev-voice-panel
:is(.gev-voice-panel-header button, select, input[type='number']) {
min-height: 2rem;
box-sizing: border-box;
color: var(--text-primary);
background: rgba(255, 255, 255, 0.04);
border: 1px solid var(--glass-border);
border-radius: 0.3rem;
font: inherit;
}
#gev-voice-control .gev-voice-panel .gev-voice-panel-header button {
padding: 0 0.6rem;
color: var(--text-secondary);
font-family: var(--font-mono);
font-size: 0.68rem;
letter-spacing: 0.04em;
line-height: 1;
cursor: pointer;
}
#gev-voice-control #gev-voice-settings-close {
width: 2rem;
padding: 0;
font-size: 1rem;
}
#gev-voice-control .gev-voice-panel-header button:hover {
color: var(--text-primary);
border-color: var(--voice-panel-accent, var(--accent));
}
#gev-voice-control .gev-voice-panel [role='tab'][aria-selected='true'] {
color: var(--voice-panel-accent, var(--accent));
border-color: currentColor;
box-shadow: 0 -2px 0 currentColor inset;
}
#gev-voice-control .gev-voice-panel [role='tab']:focus,
#gev-voice-control .gev-voice-panel :focus-visible {
outline: 2px solid var(--voice-panel-accent, var(--accent));
outline-offset: 2px;
}
.gev-voice-panel-body {
min-height: 0;
overflow: auto;
overscroll-behavior: contain;
scrollbar-width: thin;
padding: 0.55rem 0.75rem 3px 3px;
line-height: 1.45;
overflow-wrap: anywhere;
}
.gev-voice-panel-body .gev-voice-card-who {
overflow-wrap: break-word;
}
.gev-voice-panel-body p {
margin: 0;
}
.gev-voice-preference {
display: flex;
align-items: center;
gap: 0.5rem;
margin: 0;
color: var(--text-primary);
}
.gev-voice-preference-toggle {
flex-direction: row-reverse;
justify-content: space-between;
min-height: 2.75rem;
font-weight: 600;
cursor: pointer;
}
.gev-voice-preference-toggle input {
flex: 0 0 auto;
width: 18px;
height: 18px;
margin: 0;
accent-color: var(--voice-panel-accent, var(--accent));
}
.gev-voice-setting-title {
margin-bottom: 0.4rem;
font-weight: 600;
}
.gev-voice-timeout-group {
margin-top: 0.65rem;
padding-top: 0.65rem;
border-top: 1px solid var(--glass-border);
}
.gev-voice-timeout-controls {
display: flex;
align-items: center;
gap: 0.4rem;
}
#gev-voice-timeout-select {
flex: 1 1 auto;
min-width: 0;
padding: 0 0.45rem;
}
#gev-voice-timeout-input {
flex: 0 0 4.5rem;
width: 4.5rem;
padding: 0 0.45rem;
font-variant-numeric: tabular-nums;
}
.gev-voice-timeout-unit {
order: 1;
color: var(--text-dim);
font-size: 0.63rem;
}
.gev-voice-timeout-controls:has(#gev-voice-timeout-input[hidden])
.gev-voice-timeout-unit {
display: none;
}
.gev-voice-settings-note {
color: rgba(190, 221, 230, 0.72);
font-size: 0.63rem;
line-height: 1.45;
}
.gev-voice-settings-note:not(:empty) {
margin-top: 0.25rem;
}
#gev-voice-control .gev-voice-panel .gev-voice-card {
position: static;
width: 100%;
max-height: none;
overflow: visible;
padding: 0;
border: 0;
background: transparent;
box-shadow: none;
transform: none;
animation: none;
}
#command-dock > #gev-voice-control:has(.gev-voice-panel:not([hidden])) {
z-index: 101;
}
#gev-voice-control:has(.gev-voice-panel:not([hidden])) .gev-voice-help-tray {
display: none;
}
#command-dock #gev-voice-control[data-provider-choice='true'] .gev-voice-panel {
bottom: min(
calc(100% + 4.8rem + var(--gev-provider-lift)),
calc(100dvh - 7.75rem)
);
}
#command-dock.dock-has-pinned-tray
#gev-voice-control:not([data-provider-choice='true'])
.gev-voice-panel {
bottom: min(
calc(100% + 1.7rem + var(--dock-pinned-stack-height)),
calc(100dvh - 18rem)
);
}
@media (pointer: coarse) {
#gev-voice-control
.gev-voice-panel
:is(
.gev-voice-panel-header button,
select,
input[type='number'],
.gev-voice-card-dismiss
) {
min-height: 44px;
}
#gev-voice-control
.gev-voice-panel
:is(.gev-voice-panel-header button, .gev-voice-card-dismiss) {
min-width: 44px;
}
}
/* A connection error remains reachable even while the manual panel is open. */
#gev-voice-control:has(.gev-voice-panel) .gev-voice-error-tray {
z-index: 12;
}
+324 -1
View File
@@ -111,7 +111,7 @@ html:not([lang='en']) #command-dock .gev-voice-cost {
padding-left: 0.1rem;
}
html:not([lang='en']) #command-dock .gev-voice-tier-btn {
html:not([lang='en']) #command-dock .gev-voice-heading .gev-voice-tier-btn {
min-width: 0;
padding: 0 0.2rem;
}
@@ -232,3 +232,326 @@ html:not([lang='en'])
pointer-events: auto;
}
}
/* The opt-in provider and gear share a discoverable row at the existing anchor. */
#gev-voice-control:has(> .gev-voice-settings-button) {
--gev-voice-settings-gap: 0.75rem;
--gev-voice-tray-clearance: 0px;
}
#gev-voice-control:has(> .gev-voice-settings-button)
.gev-voice-provider-field:not([hidden]) {
bottom: calc(
100% + var(--gev-provider-lift, 0px) + var(--gev-voice-settings-gap)
);
display: flex;
align-items: center;
gap: 6px;
padding: 6px 46px 6px 6px;
border-color: rgba(123, 189, 211, 0.28);
background: rgba(9, 18, 27, 0.88);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.46);
color-scheme: dark;
opacity: 1;
pointer-events: auto;
}
#gev-voice-control:has(> .gev-voice-settings-button)
.gev-voice-provider-field
> span {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
#gev-voice-control:has(> .gev-voice-settings-button)
.gev-voice-provider-field
select {
min-height: 34px;
font-size: 0.7rem;
}
#gev-voice-control .gev-voice-settings-button {
position: absolute;
top: 1rem;
right: 0.25rem;
z-index: 11;
display: inline-flex;
align-items: center;
justify-content: center;
width: 34px;
height: 34px;
min-width: 34px;
min-height: 34px;
padding: 0;
color: rgba(190, 221, 230, 0.72);
background: rgba(9, 18, 27, 0.92);
border: 1px solid var(--glass-border);
border-radius: 0.3rem;
font-size: 18px;
line-height: 1;
cursor: pointer;
}
#gev-voice-control[data-provider-choice='true']:has(
.gev-voice-provider-field:not([hidden])
)
.gev-voice-settings-button {
top: auto;
right: 7px;
bottom: calc(
100% + var(--gev-provider-lift, 0px) + var(--gev-voice-settings-gap) + 7px
);
}
#gev-voice-control
.gev-voice-settings-button:is(:hover, [aria-expanded='true']) {
color: var(--voice-panel-accent, var(--accent));
border-color: currentColor;
background: rgba(0, 212, 255, 0.12);
}
#gev-voice-control .gev-voice-settings-button:focus-visible {
outline: 2px solid var(--voice-panel-accent, var(--accent));
outline-offset: 2px;
}
:root[data-ui-theme='cyber']
#gev-voice-control:has(> .gev-voice-settings-button) {
--voice-panel-accent: var(--cyber-red-bright);
}
:root[data-ui-theme='cyber']
#gev-voice-control:has(> .gev-voice-settings-button)
.gev-voice-provider-field {
background: var(--cyber-panel-surface);
border-color: var(--glass-border);
}
@media (pointer: coarse) {
#gev-voice-control .gev-voice-settings-button {
width: 44px;
height: 44px;
min-width: 44px;
min-height: 44px;
}
#gev-voice-control:has(> .gev-voice-settings-button)
.gev-voice-provider-field:not([hidden]) {
padding-right: 56px;
}
#gev-voice-control:has(> .gev-voice-settings-button)
.gev-voice-provider-field
select {
min-height: 44px;
}
}
/* Keep the row and popup on the same lift, with a small gap above pinned
trays. On very short screens the shared lift still yields to the viewport. */
#command-dock.dock-has-pinned-tray
#gev-voice-control[data-provider-choice='true']:has(
> .gev-voice-settings-button
) {
--gev-voice-settings-gap: 0.375rem;
--gev-provider-lift: min(
calc(var(--dock-pinned-stack-height) + var(--gev-voice-tray-clearance)),
max(0px, calc(100dvh - 20rem))
);
}
/* Match the attribution-aware tray offsets without moving the trays. The
popup can scroll before the shared viewport budget caps its lift. */
@media (max-width: 900px) {
#gev-voice-control:has(> .gev-voice-settings-button) {
--gev-voice-tray-clearance: calc(3rem - 1.2rem);
}
}
@media (max-width: 720px) {
#gev-voice-control:has(> .gev-voice-settings-button) {
--gev-voice-tray-clearance: calc(2vh + 2.5rem - 1.2rem);
}
}
/* Short touch landscapes have room beside the dock, not above its pinned
stack. Keep the same chooser and gear together in that empty lower gutter;
the open popup remains a bounded, scrollable overlay above the microphone. */
@media (pointer: coarse) and (orientation: landscape) and (min-width: 480px) and (max-height: 500px) {
#command-dock
> #gev-voice-control[data-provider-choice='true']:has(
> .gev-voice-settings-button
),
#command-dock.dock-has-pinned-tray
> #gev-voice-control[data-provider-choice='true']:has(
> .gev-voice-settings-button
) {
--gev-provider-lift: 0px;
--gev-landscape-row-width: clamp(
108px,
calc((100vw - var(--dock-unified-width)) / 2 - 0.875rem),
calc(100% - 2px)
);
}
#command-dock
> #gev-voice-control[data-provider-choice='true']:has(
> .gev-voice-settings-button
)
> .gev-voice-provider-field:not([hidden]) {
left: calc(50% - 50vw + 0.5rem);
right: auto;
bottom: 0.375rem;
width: var(--gev-landscape-row-width);
box-sizing: border-box;
}
#command-dock
> #gev-voice-control[data-provider-choice='true']:has(
> .gev-voice-settings-button
)
> .gev-voice-settings-button {
left: calc(50% - 50vw + 0.5rem + var(--gev-landscape-row-width) - 51px);
right: auto;
top: auto;
bottom: calc(0.375rem + 7px);
}
}
/* The revealed row clears pinned wings without making the Voice column's
noninteractive gaps cover their controls. Keep hit surfaces stable so hover
promotion cannot oscillate when the pointer crosses root padding. */
#command-dock
> #gev-voice-control[data-provider-choice='true']:has(
> .gev-voice-settings-button
) {
pointer-events: none;
}
#command-dock
> #gev-voice-control[data-provider-choice='true']:has(
> .gev-voice-settings-button
)
> :is(
#gev-voice-button,
.gev-voice-heading,
.gev-voice-readout,
.gev-voice-provider-field,
.gev-voice-settings-button,
.gev-voice-panel,
.gev-voice-error-tray
) {
pointer-events: auto;
}
@media (hover: hover) and (pointer: fine) {
#command-dock.dock-has-pinned-tray
> #gev-voice-control[data-provider-choice='true']:has(
> .gev-voice-settings-button
):is(
:hover,
[data-row-grace],
:has(
:focus-visible,
#gev-voice-provider:focus,
.gev-voice-panel[data-view='settings']:not([hidden])
)
) {
z-index: 101;
}
}
@media (hover: none), (pointer: coarse) {
#command-dock.dock-has-pinned-tray
> #gev-voice-control[data-provider-choice='true']:has(
> .gev-voice-settings-button
) {
z-index: 101;
}
}
/* Pointer grace crosses pinned trays without covering their controls. Keyboard
focus, the provider select and an open panel hold the shared row visible. */
@media (hover: hover) and (pointer: fine) {
#command-dock
#gev-voice-control[data-provider-choice='true']:has(
> .gev-voice-settings-button
)
:is(.gev-voice-provider-field, .gev-voice-settings-button) {
transition:
opacity 160ms ease,
transform 180ms ease;
}
#command-dock
#gev-voice-control[data-provider-choice='true']:has(
> .gev-voice-settings-button
):not(:hover):not([data-row-grace]):not(
:has(
:focus-visible,
#gev-voice-provider:focus,
.gev-voice-panel[data-view='settings']:not([hidden])
)
)
:is(.gev-voice-provider-field, .gev-voice-settings-button) {
opacity: 0;
pointer-events: none;
transform: translateY(8px);
}
#command-dock:not(.dock-has-pinned-tray)
#gev-voice-control[data-provider-choice='true']:has(
> .gev-voice-settings-button
)
.gev-voice-provider-field::after {
content: '';
position: absolute;
top: 100%;
right: 0;
left: 0;
height: calc(var(--gev-voice-settings-gap) + 8px);
}
}
/* Existing tier and estimate controls are readable Settings content in panel mode. */
.gev-voice-cost-settings {
display: grid;
gap: 0.55rem;
margin-top: 0.75rem;
padding-top: 0.7rem;
border-top: 1px solid var(--glass-border);
}
.gev-voice-cost-settings:has(#gev-voice-tier[hidden]):has(
#gev-voice-cost-value[hidden]
),
.gev-voice-cost-setting-row:has(> [hidden]) {
display: none;
}
.gev-voice-cost-setting-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
min-height: 2rem;
color: var(--text-primary);
font-size: 0.68rem;
}
.gev-voice-cost-setting-row > span {
min-width: 0;
}
#gev-voice-control .gev-voice-cost-settings .gev-voice-tier-btn {
flex: 0 0 auto;
min-height: 2rem;
min-width: 3.25rem;
padding: 0.3rem 0.55rem;
font-size: 0.67rem;
}
.gev-voice-cost-settings .gev-voice-cost-value {
flex: 0 0 auto;
font-size: 0.75rem;
}
.gev-voice-cost-settings .gev-voice-cost-value[data-level='ok'] {
color: var(--voice-panel-accent, var(--accent));
}
#gev-voice-control .gev-voice-settings-icon {
font-size: 18px;
line-height: 1;
}
@media (pointer: coarse) {
#gev-voice-control .gev-voice-cost-settings .gev-voice-tier-btn {
min-height: 44px;
min-width: 44px;
}
}
@media (prefers-reduced-motion: reduce) {
#command-dock
#gev-voice-control:has(> .gev-voice-settings-button)
:is(.gev-voice-provider-field, .gev-voice-settings-button) {
transition: none;
}
}