fix(desktop): even glass field via one painter; raise overlays; darken clear scrim
Session panes stayed nearly opaque under glass while the landing page and overlay cards showed the effect: the field surfaces nest (body, pane container, chat section, transcript wrapper all wear the surface tokens), so a per-token tint stacked once per layer and compounded toward opaque exactly where the pane tree is deepest. Body now paints the glass tint once and the field tokens go fully transparent, so the field alpha is one number on every route. Overlay cards on OverlayView are marked data-glass-raised and pinned near-opaque (never thinner than the field), inverting the hierarchy the first cut had backwards: glass field behind, solid card in front. Mask surfaces (diff gutter, dragged sidebar row, inline edit box) get opaque fills back, and in clear mode the overlay scrim darkens and widens its blur so two uniformly faded layers of text stop fighting.
This commit is contained in:
@@ -98,6 +98,10 @@ export function OverlayView({
|
||||
'relative flex h-full min-h-0 flex-col overflow-hidden rounded-xl border border-(--ui-stroke-secondary) bg-(--ui-chat-surface-background) shadow-md',
|
||||
rootClassName
|
||||
)}
|
||||
// Marks the card as a RAISED surface for window glass: while the field
|
||||
// behind it thins to show the desktop, this card stays near-opaque
|
||||
// (see the [data-glass-raised] rules in styles.css). Inert otherwise.
|
||||
data-glass-raised=""
|
||||
>
|
||||
<div className="pointer-events-none absolute inset-x-0 top-0 z-10 h-[calc(var(--titlebar-height)+0.1875rem)] [-webkit-app-region:drag]">
|
||||
{headerContent && (
|
||||
|
||||
@@ -105,6 +105,22 @@ describe('window translucency lever', () => {
|
||||
setTranslucencyMode('clear')
|
||||
expect(document.documentElement.hasAttribute('data-hermes-glass')).toBe(false)
|
||||
})
|
||||
|
||||
it('marks clear mode on <html> so the overlay scrim can compensate', () => {
|
||||
setTranslucency(50)
|
||||
setTranslucencyMode('clear')
|
||||
expect(document.documentElement.hasAttribute('data-hermes-clear')).toBe(true)
|
||||
|
||||
setTranslucency(0)
|
||||
expect(document.documentElement.hasAttribute('data-hermes-clear')).toBe(false)
|
||||
|
||||
setTranslucency(50)
|
||||
|
||||
if (GLASS_SUPPORTED) {
|
||||
setTranslucencyMode('glass')
|
||||
expect(document.documentElement.hasAttribute('data-hermes-clear')).toBe(false)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('glassSurfaceKeep', () => {
|
||||
|
||||
@@ -86,6 +86,11 @@ const applyGlassSurfaces = (intensity: number, mode: TranslucencyMode): void =>
|
||||
|
||||
const root = document.documentElement
|
||||
const on = mode === 'glass' && intensity > 0 && GLASS_SUPPORTED && isChatWindow()
|
||||
// Clear mode fades the whole window uniformly, so overlay text and the
|
||||
// covered transcript blend; styles.css strengthens the overlay scrim while
|
||||
// this attribute is present. Native opacity applies in every window kind,
|
||||
// so no chat-window gate.
|
||||
const clearOn = mode === 'clear' && intensity > 0
|
||||
|
||||
if (on) {
|
||||
root.setAttribute('data-hermes-glass', '')
|
||||
@@ -94,6 +99,12 @@ const applyGlassSurfaces = (intensity: number, mode: TranslucencyMode): void =>
|
||||
root.removeAttribute('data-hermes-glass')
|
||||
root.style.removeProperty('--translucency-glass-keep')
|
||||
}
|
||||
|
||||
if (clearOn) {
|
||||
root.setAttribute('data-hermes-clear', '')
|
||||
} else {
|
||||
root.removeAttribute('data-hermes-clear')
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
|
||||
@@ -562,18 +562,20 @@
|
||||
Set on <html> by store/translucency.ts; macOS chat windows only. Every
|
||||
chat window already carries an NSVisualEffectView (`vibrancy: 'sidebar'`
|
||||
in electron/main.ts) that WindowServer composites BELOW the web contents;
|
||||
the page normally buries it under fully opaque surfaces. Glass thins
|
||||
exactly the two field surfaces — the chat surface and the sidebar — to
|
||||
the page normally buries it under fully opaque surfaces.
|
||||
|
||||
ONE PAINTER: <body> paints the glass tint exactly once, at
|
||||
`--translucency-glass-keep` (set inline from the intensity slider, floor
|
||||
30%) so the material shows through as a matte desktop blur, while cards,
|
||||
popovers, the composer and every raised control keep their opaque fills
|
||||
and text keeps full contrast. Unlike Clear, nothing on the page fades.
|
||||
Surfaces that MASK in-page content are deliberately not thinned: the
|
||||
editor surface backs the diff gutter's sticky line-number mask and the
|
||||
active pane tab, and the find bar reads --ui-surface-background —
|
||||
in-page transparency there reads as text bleeding through text, not as
|
||||
glass. The two thinned surfaces already cover the window, so the pane
|
||||
container behind them never shows.
|
||||
30%), and the field tokens (chat / sidebar / editor surface) go fully
|
||||
transparent. The field surfaces NEST — body > pane container > chat
|
||||
section > transcript wrapper all wear these tokens — so thinning the
|
||||
tokens themselves stacks the tint once per layer and a session pane ends
|
||||
up near-opaque (~0.93 at 60%) while the landing page, with fewer layers,
|
||||
reads far clearer. With a single painter the field alpha is the same
|
||||
number on every route. Raised content (cards, popovers, bubbles, the
|
||||
composer) keeps its own opaque fills over full-contrast text; regions
|
||||
still read distinct through their borders and the material itself.
|
||||
Unlike Clear, nothing on the page fades.
|
||||
|
||||
The boot script in index.html pins an opaque inline background on <html>
|
||||
before first paint; it has to go transparent here or it sits behind the
|
||||
@@ -583,17 +585,69 @@
|
||||
which does not composite page alpha. */
|
||||
:root[data-hermes-glass] {
|
||||
background-color: transparent !important;
|
||||
--ui-chat-surface-background: transparent;
|
||||
--ui-sidebar-surface-background: transparent;
|
||||
--ui-editor-surface-background: transparent;
|
||||
--ui-terminal-surface-background: var(--ui-bg-chrome);
|
||||
}
|
||||
|
||||
:root[data-hermes-glass] body {
|
||||
background: color-mix(in srgb, var(--ui-bg-chrome) var(--translucency-glass-keep, 100%), transparent);
|
||||
}
|
||||
|
||||
/* Surfaces that MASK sibling content must stay filled under glass — a
|
||||
see-through mask reads as text bleeding through text, not as glass. The
|
||||
diff panel's sticky line-number gutter wears the editor surface
|
||||
(components/chat/diff-lines.tsx), and a dragged sidebar row wears the
|
||||
sidebar surface precisely so the rows it passes over don't show through
|
||||
(see the comment in sidebar/session-row.tsx). Both get an opaque fill
|
||||
back. */
|
||||
:root[data-hermes-glass] [data-slot='file-diff-panel'] {
|
||||
--ui-editor-surface-background: var(--ui-bg-chrome);
|
||||
}
|
||||
|
||||
:root[data-hermes-glass] .cursor-grabbing {
|
||||
--ui-sidebar-surface-background: var(--ui-bg-sidebar);
|
||||
}
|
||||
|
||||
/* The inline user-message edit box wears the chat surface as a raised box
|
||||
fill; give it the opaque chrome the token resolves to outside glass. */
|
||||
:root[data-hermes-glass] .composer-human-message-container {
|
||||
--ui-chat-surface-background: var(--ui-bg-chrome);
|
||||
}
|
||||
|
||||
/* Overlay cards (settings, agents, cron, command center — everything on
|
||||
OverlayView) sit ABOVE the field, so they run counter to it: near-opaque
|
||||
over a glass field, never thinner than the field itself (max() covers
|
||||
low slider values where the field keeps more than 94%). The card wears
|
||||
the chat token and its internal nav asides wear the sidebar token, so
|
||||
both come back as almost-solid fills and the two-tone layout survives. */
|
||||
:root[data-hermes-glass] [data-glass-raised] {
|
||||
--ui-chat-surface-background: color-mix(
|
||||
in srgb,
|
||||
var(--ui-bg-chrome) var(--translucency-glass-keep, 100%),
|
||||
var(--ui-bg-chrome) max(94%, var(--translucency-glass-keep, 100%)),
|
||||
transparent
|
||||
);
|
||||
--ui-sidebar-surface-background: color-mix(
|
||||
in srgb,
|
||||
var(--ui-bg-sidebar) var(--translucency-glass-keep, 100%),
|
||||
var(--ui-bg-sidebar) max(94%, var(--translucency-glass-keep, 100%)),
|
||||
transparent
|
||||
);
|
||||
--ui-terminal-surface-background: var(--ui-bg-chrome);
|
||||
--ui-editor-surface-background: var(--ui-bg-chrome);
|
||||
}
|
||||
|
||||
/* Clear mode (native window opacity) fades EVERYTHING in the window
|
||||
uniformly, so when an overlay opens over a session two layers of text
|
||||
fade by the same amount and fight through the overlay's light default
|
||||
scrim (bg-black/22 + 2px blur). While clear is active, the scrim works
|
||||
for a living: darken it and widen the blur so the covered transcript
|
||||
drops away and the overlay's own text is the only line of type in front
|
||||
of the desktop. Set on <html> by store/translucency.ts whenever
|
||||
mode='clear' and intensity > 0; !important because the utility classes
|
||||
live in a later cascade layer. */
|
||||
:root[data-hermes-clear] [data-overlay-surface] {
|
||||
background-color: rgb(0 0 0 / 0.55) !important;
|
||||
backdrop-filter: blur(0.5rem) !important;
|
||||
}
|
||||
|
||||
* {
|
||||
|
||||
Reference in New Issue
Block a user