mirror of
https://github.com/block/buzz.git
synced 2026-10-31 06:21:34 +00:00
desktop-new: glass is one material, applied whole
A glass surface is four things at once — a translucent fill, a blur behind it, a directional rim, and sometimes an outer lift — and they were four separate things a consumer assembled by hand. Every consumer did, differently. The four chrome surfaces meant to match disagreed about whether to carry an outer shadow, and four inner panels set a translucent fill with no blur at all. `glass-primary` and `glass-secondary` are Tailwind `@utility` blocks carrying the whole material, plus `-interactive` variants that add the hover. Named by stacking depth rather than by component: an earlier cut named them chrome / panel / float and it was wrong twice over, because `chrome` and `float` both landed on glass-3 — 66% opaque in dark, which is why chrome did not read as glass — and naming a material after a component invites a second material the first time a component wants a different depth. `box-shadow` is composed inside each material because it has to be: a rim and a lift both set it, so as separate utilities the second silently overwrites the first. That is a structural argument for bundling, not a preference. The fills are deliberately not registered as colour utilities, so a bare `bg-glass-primary` class cannot exist. Removing a redundant fill from the four workspace panels fixed something no token held. They each set the same `glass-2` as the container they exactly cover — measured 610x551 inside 612x553, the two pixels being its border — and two identical translucent layers composite: 0.52 over 0.52 is 0.77, between glass-4 and glass-5. Panels meant to be the most translucent surface in the system read as nearly solid. That in turn exposed a bug the density had been masking since the dock landed: dockview's theme sets `--dv-group-view-background-color` to a literal `white` and `DockWorkspace` passes `themeLight` in both modes, so an opaque white sheet sat behind the glass. Reset here, because every other dockview surface the variable feeds was already overridden to transparent. Signed-off-by: Morgan Martin <morganmartin@squareup.com> Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
This commit is contained in:
@@ -35,8 +35,8 @@
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border-radius: 50%;
|
||||
background: var(--bg-accent-tint);
|
||||
color: var(--text-accent);
|
||||
background: var(--purple-3);
|
||||
color: var(--purple-12);
|
||||
}
|
||||
|
||||
.agent-avatar {
|
||||
@@ -69,13 +69,16 @@
|
||||
}
|
||||
|
||||
.agent-presence[data-running] {
|
||||
background: var(--bg-success);
|
||||
background: var(--green-9);
|
||||
}
|
||||
|
||||
/* No background, for the same reason as the panels in product.css:
|
||||
this fills `.dv-content-container` (measured 610x718 inside 612x720, the two
|
||||
pixels being its border) and that container is already primary glass. Its own
|
||||
identical fill was compositing to 0.77 alpha rather than 0.52. */
|
||||
.agent-main-panel {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
background: var(--bg-panel-glass);
|
||||
}
|
||||
|
||||
.agent-profile-header {
|
||||
@@ -85,7 +88,7 @@
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding: 1.25rem 1.5rem;
|
||||
border-bottom: 1px solid var(--border-tertiary);
|
||||
border-bottom: 1px solid var(--border-primary);
|
||||
}
|
||||
|
||||
.agent-profile-title {
|
||||
@@ -132,7 +135,7 @@
|
||||
.field-stack textarea,
|
||||
.field-stack select {
|
||||
width: 100%;
|
||||
border: 1px solid var(--border-secondary);
|
||||
border: 1px solid var(--border-primary);
|
||||
border-radius: 0.75rem;
|
||||
padding: 0.75rem;
|
||||
background: var(--bg-panel);
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
border: 0;
|
||||
border-radius: var(--radius-chip);
|
||||
padding-inline: var(--space-chip-inset);
|
||||
background: var(--bg-accent-tint);
|
||||
background: var(--purple-3);
|
||||
/* Primary text, not accent text. The tint is doing the colour work, so the
|
||||
label stays the same weight as the sentence it sits in — a chip marks a
|
||||
reference, it does not shout. */
|
||||
@@ -40,11 +40,11 @@
|
||||
}
|
||||
|
||||
button.inline-chip:hover {
|
||||
background: var(--bg-accent-tint-hover);
|
||||
background: var(--purple-4);
|
||||
}
|
||||
|
||||
.inline-chip:focus-visible {
|
||||
outline: 2px solid var(--ring-focus);
|
||||
outline: 2px solid var(--purple-8);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,23 +17,111 @@
|
||||
}
|
||||
|
||||
/*
|
||||
* The glass rim. Two inset shadows rather than a gradient border, because CSS
|
||||
* borders accept only solid colours: border-image takes a gradient but ignores
|
||||
* border-radius, and the two-background background-clip trick needs an opaque
|
||||
* inner fill so it bleeds across a translucent surface. See DESIGN.md.
|
||||
* ---------------------------------------------------------------------------
|
||||
* Glass materials — PROPOSED, for review on /design/glass.
|
||||
*
|
||||
* A glass surface is four things at once: a translucent fill, a blur behind it,
|
||||
* a directional rim, and (sometimes) an outer lift. Those used to be separate,
|
||||
* so a consumer could take the fill and forget the rest — and every consumer
|
||||
* did, differently. The four chrome surfaces meant to match each assembled their
|
||||
* own material and disagreed about the outer shadow; four inner panels set a
|
||||
* translucent fill with no blur *and* no need for one, since they exactly cover
|
||||
* a surface that was already glass, so their fill composited 0.52 over 0.52 to
|
||||
* 0.77 — a value no token holds and nobody chose. These bundle the material so
|
||||
* it arrives whole or not at all.
|
||||
*
|
||||
* NAMED BY STACKING DEPTH, not by the component that uses it. An earlier cut
|
||||
* named them chrome / panel / float and it was wrong twice over: `chrome` and
|
||||
* `float` both landed on glass-3 (66% opaque in dark — two thirds solid, which
|
||||
* is why chrome did not read as glass at all), and naming a material after a
|
||||
* component invites a second material the first time a component wants a
|
||||
* different depth. Depth is the axis that actually varies.
|
||||
*
|
||||
* The rule is DESIGN.md § Surface and depth: **elevation reads as less
|
||||
* translucency, not as a lighter colour.** So the higher it sits, the more
|
||||
* opaque it gets, and the shadow separates it from what it covers.
|
||||
*
|
||||
* `box-shadow` is composed here once because it has to be: `rim-glass` and
|
||||
* `elevate-xs` both set `box-shadow`, so used together the second silently
|
||||
* overwrites the first. A rim and a lift cannot compose as separate utilities.
|
||||
*/
|
||||
@utility rim-glass {
|
||||
|
||||
/* Sits directly on the backdrop: chrome, nav, a region in glass. The most
|
||||
translucent, because the backdrop is the thing worth seeing through to. */
|
||||
@utility glass-primary {
|
||||
background: var(--bg-glass-primary);
|
||||
backdrop-filter: blur(var(--blur-md));
|
||||
box-shadow:
|
||||
inset 0 1px 0 var(--rim-lit),
|
||||
inset 0 -1px 0 var(--rim-shade);
|
||||
}
|
||||
|
||||
@utility blur-chrome {
|
||||
backdrop-filter: blur(var(--blur-md));
|
||||
/* Sits over something already glass: popovers, menus, a modal over a panel.
|
||||
Less translucent so it separates, deeper blur so what it covers is not
|
||||
legible through it, and a real shadow because it is genuinely above. */
|
||||
@utility glass-secondary {
|
||||
background: var(--bg-glass-secondary);
|
||||
backdrop-filter: blur(var(--blur-lg));
|
||||
box-shadow:
|
||||
var(--shadow-sm),
|
||||
inset 0 1px 0 var(--rim-lit),
|
||||
inset 0 -1px 0 var(--rim-shade);
|
||||
}
|
||||
|
||||
@utility blur-panel {
|
||||
/*
|
||||
* Interactive glass: the material plus its hover, for a glass surface you can
|
||||
* click. Separate utilities rather than a `:hover` inside the two above,
|
||||
* because a glass *region* is not interactive and should not react to a cursor
|
||||
* crossing it.
|
||||
*
|
||||
* A glass hover moves one step up the ramp and never changes blur —
|
||||
* re-blurring a large surface every frame is expensive enough to feel. Opacity
|
||||
* here is a step on a named ramp, not a fade applied to a colour.
|
||||
*/
|
||||
@utility glass-primary-interactive {
|
||||
background: var(--bg-glass-primary);
|
||||
backdrop-filter: blur(var(--blur-md));
|
||||
box-shadow:
|
||||
inset 0 1px 0 var(--rim-lit),
|
||||
inset 0 -1px 0 var(--rim-shade);
|
||||
|
||||
&:hover {
|
||||
background: var(--bg-glass-primary-hover);
|
||||
}
|
||||
}
|
||||
|
||||
@utility glass-secondary-interactive {
|
||||
background: var(--bg-glass-secondary);
|
||||
backdrop-filter: blur(var(--blur-lg));
|
||||
box-shadow:
|
||||
var(--shadow-sm),
|
||||
inset 0 1px 0 var(--rim-lit),
|
||||
inset 0 -1px 0 var(--rim-shade);
|
||||
|
||||
&:hover {
|
||||
background: var(--bg-glass-secondary-hover);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* `rim-glass`, `blur-panel`, `elevate-xs`, and `elevate-sm` were here and are
|
||||
* gone: every one had zero readers once the glass materials absorbed them.
|
||||
*
|
||||
* `rim-glass` and `elevate-xs` are worth remembering as a pair, because they
|
||||
* could not be composed. Both set `box-shadow`, so an element using the rim and
|
||||
* a lift together silently kept whichever Tailwind emitted second — a rim and an
|
||||
* elevation cannot be separate utilities. That is not a naming problem; it is why
|
||||
* the materials compose `box-shadow` once, internally.
|
||||
*
|
||||
* The elevation *tokens* are alive and read directly as `var(--shadow-xs)` in
|
||||
* five stylesheets. A one-declaration utility wrapping a token earns nothing.
|
||||
*
|
||||
* `blur-chrome` survives on two readers, both documentation: the /design pages
|
||||
* need a blur without a fill so a swatch can show a ramp step against the real
|
||||
* backdrop. Product code should reach for a material instead.
|
||||
*/
|
||||
@utility blur-chrome {
|
||||
backdrop-filter: blur(var(--blur-md));
|
||||
}
|
||||
|
||||
@utility texture-dots {
|
||||
@@ -44,14 +132,6 @@
|
||||
background-size: var(--texture-dots-size) var(--texture-dots-size);
|
||||
}
|
||||
|
||||
@utility elevate-xs {
|
||||
box-shadow: var(--shadow-xs);
|
||||
}
|
||||
|
||||
@utility elevate-sm {
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
/* Composition-input spike only. Remove with the spike route. */
|
||||
.spike-editor {
|
||||
outline: none;
|
||||
|
||||
@@ -47,32 +47,30 @@
|
||||
|
||||
.destination-tabs button,
|
||||
.topbar-icon-button {
|
||||
@apply glass-primary-interactive;
|
||||
|
||||
height: 1.75rem;
|
||||
border: 0;
|
||||
border-radius: 0.5625rem;
|
||||
background: var(--bg-chrome-glass);
|
||||
color: var(--text-secondary);
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
backdrop-filter: blur(var(--blur-md));
|
||||
box-shadow:
|
||||
inset 0 1px 0 var(--rim-lit),
|
||||
inset 0 -1px 0 var(--rim-shade);
|
||||
}
|
||||
|
||||
.destination-tabs button {
|
||||
padding: 0 0.75rem;
|
||||
}
|
||||
|
||||
/* The material carries the hover fill; this only carries the text colour,
|
||||
which is not part of a surface material. */
|
||||
.destination-tabs button:hover,
|
||||
.topbar-icon-button:hover {
|
||||
background: var(--bg-chrome-glass-hover);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.destination-tabs button[data-selected] {
|
||||
background: var(--bg-inverse);
|
||||
color: var(--text-on-inverse);
|
||||
background: var(--neutral-11);
|
||||
color: var(--neutral-1);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
@@ -101,12 +99,24 @@
|
||||
.buzz-dockview {
|
||||
--dv-background-color: transparent;
|
||||
--dv-tabs-and-actions-container-background-color: transparent;
|
||||
/* Dockview's own theme sets this to a literal `white`, and `DockWorkspace`
|
||||
passes `themeLight` in both colour schemes — so in dark mode the vendor
|
||||
painted an opaque white sheet *behind* our glass, and the 52% dark fill
|
||||
over it composited to flat grey. The panels' redundant second
|
||||
fill had been hiding it: two stacked fills reached 0.77 alpha, opaque
|
||||
enough that the white underneath barely showed. Removing the redundancy
|
||||
revealed a bug that had been there since the dock landed.
|
||||
|
||||
Reset here rather than swapping the JS theme, because every dockview
|
||||
surface this variable feeds is already overridden to transparent below —
|
||||
the variable was the one leak. */
|
||||
--dv-group-view-background-color: transparent;
|
||||
--dv-tab-active-background-color: var(--bg-chrome-selected);
|
||||
--dv-tab-active-color: var(--text-primary);
|
||||
--dv-tab-inactive-color: var(--text-secondary);
|
||||
--dv-separator-border: transparent;
|
||||
--dv-drag-over-background-color: var(--bg-accent-tint);
|
||||
--dv-drag-over-border-color: var(--border-accent);
|
||||
--dv-drag-over-background-color: var(--purple-3);
|
||||
--dv-drag-over-border-color: var(--purple-8);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@@ -139,37 +149,45 @@
|
||||
box-shadow: var(--shadow-xs);
|
||||
}
|
||||
|
||||
/* Dockview arranges panels; Panel owns the surface treatment. */
|
||||
.buzz-dockview .dv-content-container {
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--border-tertiary);
|
||||
border-radius: 1.5rem;
|
||||
background: var(--bg-panel-glass);
|
||||
box-shadow:
|
||||
var(--shadow-sm),
|
||||
inset 0 1px 0 var(--rim-lit),
|
||||
inset 0 -1px 0 var(--rim-shade);
|
||||
backdrop-filter: blur(var(--blur-md));
|
||||
}
|
||||
|
||||
.buzz-dockview .dv-sash {
|
||||
width: var(--space-2);
|
||||
background: transparent;
|
||||
cursor: col-resize;
|
||||
}
|
||||
|
||||
.buzz-dockview .dv-sash:hover {
|
||||
background: var(--border-accent);
|
||||
background: var(--border-primary);
|
||||
}
|
||||
|
||||
.buzz-dockview:has(.dv-tab.dv-tab-dragging) {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* No background. These fill `.dv-content-container` edge to edge — measured at
|
||||
610x551 inside 612x553, the two pixels being its border — and that container
|
||||
is already primary glass.
|
||||
|
||||
They each set the same `glass-2` fill with no blur of their own, so the two
|
||||
identical translucent layers composited: 0.52 over 0.52 is **0.77**, landing
|
||||
between glass-4 and glass-5. The panels read as nearly solid, which is why
|
||||
the backdrop barely showed through a surface that was supposed to be the
|
||||
most translucent one in the system. Nothing declared 0.77 and no token holds
|
||||
it — it was the arithmetic of a redundant fill.
|
||||
|
||||
A region that exactly covers a glass surface does not need its own. If one
|
||||
of these later needs to sit apart from its container, it takes a whole
|
||||
material rather than a bare fill. */
|
||||
.workspace-navigator,
|
||||
.session-view,
|
||||
.new-session-view,
|
||||
.empty-workspace-panel {
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
background: var(--bg-panel-glass);
|
||||
}
|
||||
|
||||
.workspace-navigator,
|
||||
@@ -186,7 +204,7 @@
|
||||
min-height: 4rem;
|
||||
align-items: center;
|
||||
padding: 0 1.25rem;
|
||||
border-bottom: 1px solid var(--border-tertiary);
|
||||
border-bottom: 1px solid var(--border-primary);
|
||||
}
|
||||
|
||||
.navigator-heading {
|
||||
@@ -262,7 +280,7 @@
|
||||
.navigator-row:hover,
|
||||
.session-child-row:hover,
|
||||
.navigator-row-action:hover {
|
||||
background: var(--bg-hover);
|
||||
background: var(--neutral-4);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
@@ -307,8 +325,8 @@
|
||||
.session-thread-line {
|
||||
width: 0.625rem;
|
||||
height: 0.875rem;
|
||||
border-bottom: 1px solid var(--border-secondary);
|
||||
border-left: 1px solid var(--border-secondary);
|
||||
border-bottom: 1px solid var(--border-primary);
|
||||
border-left: 1px solid var(--border-primary);
|
||||
border-radius: 0 0 0 0.375rem;
|
||||
}
|
||||
|
||||
@@ -368,8 +386,8 @@
|
||||
}
|
||||
|
||||
.message-avatar[data-agent] {
|
||||
background: var(--bg-accent-tint);
|
||||
color: var(--text-accent);
|
||||
background: var(--purple-3);
|
||||
color: var(--purple-12);
|
||||
}
|
||||
|
||||
.message-meta {
|
||||
@@ -399,7 +417,7 @@
|
||||
align-items: end;
|
||||
gap: 0.75rem;
|
||||
margin: auto;
|
||||
border: 1px solid var(--border-secondary);
|
||||
border: 1px solid var(--border-primary);
|
||||
border-radius: 1rem;
|
||||
padding: 0.625rem;
|
||||
background: var(--bg-float);
|
||||
@@ -433,8 +451,8 @@
|
||||
height: 2.25rem;
|
||||
place-items: center;
|
||||
border-radius: 0.75rem;
|
||||
background: var(--bg-inverse);
|
||||
color: var(--text-on-inverse);
|
||||
background: var(--neutral-11);
|
||||
color: var(--neutral-1);
|
||||
}
|
||||
|
||||
.quiet-button,
|
||||
@@ -450,8 +468,8 @@
|
||||
}
|
||||
|
||||
.primary-button {
|
||||
background: var(--bg-inverse);
|
||||
color: var(--text-on-inverse);
|
||||
background: var(--neutral-11);
|
||||
color: var(--neutral-1);
|
||||
}
|
||||
|
||||
.new-session-center {
|
||||
@@ -476,7 +494,7 @@
|
||||
|
||||
button:focus-visible,
|
||||
textarea:focus-visible {
|
||||
outline: 2px solid var(--border-accent);
|
||||
outline: 2px solid var(--purple-8);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user