diff --git a/desktop-new/src/shared/styles/agents.css b/desktop-new/src/shared/styles/agents.css index 1c4bc0f45e..912eccfa85 100644 --- a/desktop-new/src/shared/styles/agents.css +++ b/desktop-new/src/shared/styles/agents.css @@ -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); diff --git a/desktop-new/src/shared/styles/chips.css b/desktop-new/src/shared/styles/chips.css index 1e94f9a96b..4ecf376ea4 100644 --- a/desktop-new/src/shared/styles/chips.css +++ b/desktop-new/src/shared/styles/chips.css @@ -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; } diff --git a/desktop-new/src/shared/styles/globals.css b/desktop-new/src/shared/styles/globals.css index 0f91ac28df..6515f1fc4b 100644 --- a/desktop-new/src/shared/styles/globals.css +++ b/desktop-new/src/shared/styles/globals.css @@ -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; diff --git a/desktop-new/src/shared/styles/product.css b/desktop-new/src/shared/styles/product.css index 279ab1851e..213f174cec 100644 --- a/desktop-new/src/shared/styles/product.css +++ b/desktop-new/src/shared/styles/product.css @@ -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; }