Files
gods-eye-view/src/ui/styles/voice-cost.css
T

567 lines
16 KiB
CSS

/* ------------------------------------------------------------------ *
* Voice cost control — model-tier toggle + running session estimate.
* Appended (not woven into the dock passes above) so the three existing
* #gev-voice-control cascades stay untouched; these rules only add the
* new .gev-voice-cost cluster inside the existing .gev-voice-heading row.
* ------------------------------------------------------------------ */
.gev-voice-cost {
display: flex;
align-items: center;
gap: 0.22rem;
margin-left: auto; /* pin right; kicker + status keep their left cluster */
flex: 0 0 auto;
}
.gev-voice-tier-btn {
padding: 0 0.24rem;
height: 0.72rem;
min-width: 1.55rem;
display: inline-flex;
align-items: center;
justify-content: center;
background: rgba(0, 212, 255, 0.06);
border: 1px solid rgba(0, 212, 255, 0.34);
border-radius: 0.18rem;
color: var(--text-secondary);
font-family: var(--font-mono);
font-size: 0.4rem;
letter-spacing: 0.045rem;
line-height: 1;
cursor: pointer;
transition:
color var(--transition-fast),
border-color var(--transition-fast),
background var(--transition-fast);
}
.gev-voice-tier-btn:hover {
color: var(--text-primary);
border-color: rgba(0, 212, 255, 0.6);
background: rgba(0, 212, 255, 0.12);
}
/* MINI engaged — amber, distinct from the cyan default so the cheaper tier
is legible at a glance without reading the label. */
.gev-voice-tier-btn[aria-pressed='true'] {
color: #ffc46b;
border-color: rgba(255, 196, 107, 0.55);
background: rgba(255, 196, 107, 0.12);
}
.gev-voice-cost-value {
color: var(--text-dim);
font-family: var(--font-mono);
font-size: 0.4rem;
letter-spacing: 0.02rem;
line-height: 1;
font-variant-numeric: tabular-nums;
transition: color var(--transition-fast);
}
.gev-voice-cost-value[data-level='warn'] {
color: #ffc46b;
}
.gev-voice-cost-warning {
margin-left: auto;
flex: 0 0 auto;
}
.gev-voice-cost-warning[data-level='unknown'] {
color: #ffc46b;
}
.gev-voice-cost-value[data-level='cap'] {
color: #ff6b6b;
font-weight: 600;
}
/* The dock heading row is 0.8rem tall; keep the cluster inside it. */
#command-dock .gev-voice-cost {
max-height: 0.8rem;
}
/* The dock collapses the kicker/readout when a tray is open — the cost
cluster follows the same rule so the row never overflows. */
#command-dock.tray-open .gev-voice-cost {
display: none;
}
.gev-voice-tier-btn[hidden],
.gev-voice-cost-value[hidden] {
display: none;
}
/* The heading row is only as wide as the voice column: the cost cluster
keeps its size and a long state (ПОДКЛЮЧЕНИЕ, CONECTANDO) ends in an
ellipsis. Dropping the kicker returns its gap to the state label; the
negative margin reaches over the gutter beside the mic ring, which has
curved away at heading height. */
html:not([lang='en']) #command-dock .gev-voice-heading {
gap: 0.2rem;
margin-left: -0.6rem;
}
html:not([lang='en']) #command-dock .gev-voice-heading > .gev-voice-kicker {
display: none;
}
html:not([lang='en']) #command-dock .gev-voice-heading > #gev-voice-status {
flex: 0 1 auto;
min-width: 0;
overflow: hidden;
letter-spacing: 0;
text-overflow: ellipsis;
white-space: nowrap;
}
html:not([lang='en']) #command-dock .gev-voice-cost {
gap: 0.16rem;
padding-left: 0.1rem;
}
html:not([lang='en']) #command-dock .gev-voice-heading .gev-voice-tier-btn {
min-width: 0;
padding: 0 0.2rem;
}
html:not([lang='en']) #command-dock .gev-voice-cost-value {
letter-spacing: 0;
}
/* Live hints and the tier switch ("СТАНДАРТ — со следующей сессии") run
longer: a second line from the spare space under the readout, with no
tracking so the tier line does not need a third. */
html:not([lang='en'])
#command-dock
#gev-voice-control:is(
[data-status='connecting'],
[data-status='listening'],
[data-status='executing']
)
#gev-voice-detail {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
line-clamp: 2;
line-height: 1.15;
letter-spacing: 0;
white-space: normal;
overflow-wrap: anywhere;
}
#gev-voice-control[data-provider-choice='true'] {
--gev-provider-lift: 0px;
}
/* Provider choice belongs to the existing voice control, not a separate panel. */
.gev-voice-provider-field {
position: absolute;
bottom: calc(100% + var(--gev-provider-lift, 0px));
z-index: 9;
left: 0;
right: 0;
display: grid;
gap: 4px;
padding: 8px 6px;
border: 1px solid var(--glass-border);
border-radius: 8px;
background: var(--bg-panel, #09121b);
color: var(--text-secondary);
font-family: var(--font-mono);
font-size: 0.5rem;
opacity: 0;
pointer-events: none;
}
.gev-voice-provider-field[hidden] {
display: none;
}
.gev-voice-provider-field select {
min-width: 0;
width: 100%;
min-height: 30px;
color: var(--text-primary);
background: #09121b;
border: 1px solid var(--glass-border);
border-radius: 4px;
font: inherit;
font-size: 0.65rem;
}
#gev-voice-control:hover .gev-voice-provider-field,
#gev-voice-control:focus-within .gev-voice-provider-field {
opacity: 1;
pointer-events: auto;
}
#command-dock
#gev-voice-control[data-provider-choice='true']
.gev-voice-help-tray,
#command-dock #gev-voice-control[data-provider-choice='true'] .gev-voice-card,
#command-dock
#gev-voice-control[data-provider-choice='true']
.gev-voice-error-tray {
bottom: calc(100% + 4.8rem + var(--gev-provider-lift));
}
#command-dock.dock-has-pinned-tray
#gev-voice-control[data-provider-choice='true'] {
--gev-provider-lift: calc(1.7rem + var(--dock-pinned-stack-height));
}
/* Pinned trays separate the chooser from its hover owner. Keep it reachable
across that gap without requiring keyboard focus to hold it open. */
#command-dock.dock-has-pinned-tray .gev-voice-provider-field {
opacity: 1;
pointer-events: auto;
}
@media (max-width: 900px) {
/* The attribution clearance also creates a gap for mouse users in a
narrow window, so the raised chooser must not depend on root hover. */
.gev-voice-provider-field {
opacity: 1;
pointer-events: auto;
}
#command-dock #gev-voice-control[data-provider-choice='true'] {
--gev-provider-lift: 3rem;
}
#command-dock.dock-has-pinned-tray
#gev-voice-control[data-provider-choice='true'] {
--gev-provider-lift: calc(3.5rem + var(--dock-pinned-stack-height));
}
}
@media (max-width: 720px) {
#command-dock #gev-voice-control[data-provider-choice='true'] {
--gev-provider-lift: calc(2vh + 2.5rem);
}
#command-dock.dock-has-pinned-tray
#gev-voice-control[data-provider-choice='true'] {
--gev-provider-lift: calc(2vh + 3rem + var(--dock-pinned-stack-height));
}
}
@media (hover: none) {
.gev-voice-provider-field {
opacity: 1;
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;
}
}