mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-30 11:52:04 +00:00
fix(web): remove marketing backdrop stack for lighter dashboard shell
Drop the CSS lens overlay (blend modes, noise, inversion) and backdrop-blur from the ops dashboard so compositing no longer competes with xterm on /chat. Use flat theme backgrounds and direct Nous Blue palette colors instead of FG-inversion authoring. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
b963d3238b
commit
57d98ebed7
21 changed files with 50 additions and 299 deletions
|
|
@ -59,7 +59,6 @@ import { SelectionSwitcher } from "@nous-research/ui/ui/components/selection-swi
|
|||
import { Spinner } from "@nous-research/ui/ui/components/spinner";
|
||||
import { Typography } from "@nous-research/ui/ui/components/typography/index";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Backdrop } from "@/components/Backdrop";
|
||||
import { SidebarFooter } from "@/components/SidebarFooter";
|
||||
import { SidebarStatusStrip, gatewayLine } from "@/components/SidebarStatusStrip";
|
||||
import { useBelowBreakpoint } from "@nous-research/ui/hooks/use-below-breakpoint";
|
||||
|
|
@ -483,18 +482,16 @@ export default function App() {
|
|||
<ProfileProvider>
|
||||
<div
|
||||
data-layout-variant={layoutVariant}
|
||||
className="flex h-dvh max-h-dvh min-h-0 flex-col overflow-hidden bg-black text-text-primary antialiased"
|
||||
className="flex h-dvh max-h-dvh min-h-0 flex-col overflow-hidden bg-background-base text-text-primary antialiased"
|
||||
>
|
||||
<SelectionSwitcher />
|
||||
<Backdrop />
|
||||
<PluginSlot name="backdrop" />
|
||||
|
||||
<header
|
||||
className={cn(
|
||||
"lg:hidden fixed top-0 left-0 right-0 z-40 min-h-14",
|
||||
"flex items-center gap-2 px-4 py-2",
|
||||
"border-b border-current/20",
|
||||
"bg-background-base/90 backdrop-blur-sm",
|
||||
"bg-background-base",
|
||||
)}
|
||||
style={{
|
||||
background: "var(--component-header-background)",
|
||||
|
|
@ -514,10 +511,7 @@ export default function App() {
|
|||
<Menu />
|
||||
</Button>
|
||||
|
||||
<Typography
|
||||
className="font-bold text-[0.95rem] leading-[0.95] tracking-[0.05em] text-midground"
|
||||
style={{ mixBlendMode: "plus-lighter" }}
|
||||
>
|
||||
<Typography className="font-bold text-[0.95rem] leading-[0.95] tracking-[0.05em] text-midground">
|
||||
{t.app.brand}
|
||||
</Typography>
|
||||
</header>
|
||||
|
|
@ -529,7 +523,7 @@ export default function App() {
|
|||
onClick={closeMobile}
|
||||
className={cn(
|
||||
"lg:hidden fixed inset-0 z-40 p-0 block",
|
||||
"bg-black/60 backdrop-blur-sm",
|
||||
"bg-black/70",
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
|
@ -545,7 +539,7 @@ export default function App() {
|
|||
className={cn(
|
||||
"fixed top-0 left-0 z-50 flex h-dvh max-h-dvh w-64 min-h-0 flex-col",
|
||||
"border-r border-current/20",
|
||||
"bg-background-base/95 backdrop-blur-sm",
|
||||
"bg-background-base",
|
||||
"transition-[transform] duration-200 ease-out",
|
||||
mobileOpen ? "translate-x-0" : "-translate-x-full",
|
||||
"lg:sticky lg:top-0 lg:translate-x-0 lg:shrink-0 lg:overflow-hidden",
|
||||
|
|
@ -573,10 +567,7 @@ export default function App() {
|
|||
>
|
||||
<PluginSlot name="header-left" />
|
||||
|
||||
<Typography
|
||||
className="font-bold text-[1.125rem] leading-[0.95] tracking-[0.0525rem] text-midground uppercase"
|
||||
style={{ mixBlendMode: "plus-lighter" }}
|
||||
>
|
||||
<Typography className="font-bold text-[1.125rem] leading-[0.95] tracking-[0.0525rem] text-midground uppercase">
|
||||
Hermes
|
||||
<br />
|
||||
Agent
|
||||
|
|
@ -879,7 +870,6 @@ function SidebarNavLink({
|
|||
<span
|
||||
aria-hidden
|
||||
className="absolute left-0 top-0 bottom-0 w-px bg-midground"
|
||||
style={{ mixBlendMode: "plus-lighter" }}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
|
@ -1050,7 +1040,6 @@ function SystemActionButton({
|
|||
<span
|
||||
aria-hidden
|
||||
className="absolute left-0 top-0 bottom-0 w-px bg-midground"
|
||||
style={{ mixBlendMode: "plus-lighter" }}
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
|
|
@ -1186,7 +1175,7 @@ function SidebarTooltip({ anchor, label, warmRef }: SidebarTooltipProps) {
|
|||
className={cn(
|
||||
"fixed z-[100] pointer-events-none",
|
||||
"px-2 py-1",
|
||||
"bg-background-base/95 border border-current/20 backdrop-blur-sm shadow-lg",
|
||||
"bg-background-base border border-current/20 shadow-lg",
|
||||
"font-mondwest text-display text-xs tracking-[0.1em] text-midground uppercase",
|
||||
)}
|
||||
style={{
|
||||
|
|
|
|||
|
|
@ -1,137 +0,0 @@
|
|||
import { useGpuTier } from "@nous-research/ui/hooks/use-gpu-tier";
|
||||
|
||||
import fillerBgUrl from "@nous-research/ui/assets/filler-bg0.webp";
|
||||
|
||||
/**
|
||||
* Replicates the visual layer stack of `<Overlays dark />` from
|
||||
* `@nous-research/ui` without pulling in its leva / gsap / three peer deps.
|
||||
*
|
||||
* See `design-language/src/ui/components/overlays/index.tsx` for the source of
|
||||
* truth. Defaults match LENS_0 (the Hermes teal dark preset); the deep canvas
|
||||
* and the warm vignette both read theme-switchable CSS custom properties so
|
||||
* `ThemeProvider` can repaint the stack without remounting.
|
||||
*
|
||||
* z-1 bg = `var(--background-base)`, mix-blend-mode driven by
|
||||
* `--component-backdrop-bg-blend-mode` (default `difference`).
|
||||
* Both LENS_0-style dark themes and the LENS_5I-style Nous Blue
|
||||
* light theme keep `difference` here — the canvas is flipped by
|
||||
* the z-200 FG inversion layer, not by changing this blend mode.
|
||||
* The CSS var is exposed as a hook so future presets can override
|
||||
* it (e.g. `multiply` to paint the bg as-is before inversion)
|
||||
* without touching this component.
|
||||
* z-2 bundled filler-bg WebP, inverted, opacity 0.033, difference
|
||||
* z-99 warm top-left vignette (`var(--warm-glow)`), opacity 0.22, lighten
|
||||
* z-200 FG inversion = `var(--foreground)` (opaque white in LENS_5I,
|
||||
* alpha-0 in LENS_0), mix-blend-mode: difference. This is the
|
||||
* layer that flips the dashboard into "light mode" for inverted
|
||||
* themes; for normal dark themes its alpha is 0 so it's a no-op.
|
||||
* Deliberately placed above every UI overlay z-index (modals,
|
||||
* tooltips, and dropUp dropdowns all sit at z-[100]) so portaled
|
||||
* elements get inverted along with the rest of the page instead
|
||||
* of painting with pre-inversion colors on top of the lens.
|
||||
* z-201 noise grain (SVG, ~55% opacity × `--noise-opacity-mul`,
|
||||
* color-dodge) — gated on GPU tier. Sits above the inversion
|
||||
* layer by design so the grain is not flipped.
|
||||
*
|
||||
* `useGpuTier` returns 0 when WebGL is unavailable, the renderer is a
|
||||
* software rasterizer (SwiftShader/llvmpipe), or the user has
|
||||
* `prefers-reduced-motion: reduce` set. We skip the animated noise layer
|
||||
* in that case so low-power / accessibility-conscious sessions stay crisp,
|
||||
* mirroring the DS `<Noise />` component's own opt-out.
|
||||
*/
|
||||
export function Backdrop() {
|
||||
const gpuTier = useGpuTier();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
aria-hidden
|
||||
className="pointer-events-none fixed inset-0 z-[1]"
|
||||
style={
|
||||
{
|
||||
backgroundColor: "var(--background-base)",
|
||||
mixBlendMode:
|
||||
"var(--component-backdrop-bg-blend-mode, difference)",
|
||||
} as unknown as React.CSSProperties
|
||||
}
|
||||
/>
|
||||
|
||||
<div
|
||||
aria-hidden
|
||||
className="pointer-events-none fixed inset-0 z-[2]"
|
||||
style={
|
||||
{
|
||||
// Themes can override the filler background by setting
|
||||
// `assets.bg` — the <img> hides itself when a CSS bg is set
|
||||
// so the two don't double-darken. CSS var fallbacks keep the
|
||||
// default behaviour unchanged when no theme customises these.
|
||||
mixBlendMode:
|
||||
"var(--component-backdrop-filler-blend-mode, difference)",
|
||||
opacity: "var(--component-backdrop-filler-opacity, 0.033)",
|
||||
backgroundImage: "var(--theme-asset-bg)",
|
||||
backgroundSize: "var(--component-backdrop-background-size, cover)",
|
||||
backgroundPosition:
|
||||
"var(--component-backdrop-background-position, center)",
|
||||
} as unknown as React.CSSProperties
|
||||
}
|
||||
>
|
||||
<img
|
||||
alt=""
|
||||
className="h-[150dvh] w-auto min-w-[100dvw] object-cover object-top-left invert theme-default-filler"
|
||||
fetchPriority="low"
|
||||
src={fillerBgUrl}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
aria-hidden
|
||||
className="pointer-events-none fixed inset-0 z-[99]"
|
||||
style={{
|
||||
background:
|
||||
"radial-gradient(ellipse at 0% 0%, transparent 60%, var(--warm-glow) 100%)",
|
||||
mixBlendMode: "lighten",
|
||||
opacity: 0.22,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Foreground inversion layer. Source-of-truth: LENS_5I.Lens.fgOpacity
|
||||
+ fgBlend: 'difference' in `design-language/src/ui/components/
|
||||
overlays/lens.ts`. With `--foreground-alpha: 0` (LENS_0 dark default)
|
||||
the layer is fully transparent and contributes nothing; with
|
||||
alpha 1 + opaque white it inverts the entire stack below it,
|
||||
producing the LENS_5I "light mode" look without altering any
|
||||
downstream component code.
|
||||
|
||||
z-200 (not 100) so it sits above every portaled UI overlay —
|
||||
sidebar tooltips, dropUp dropdowns, and modal dialogs all use
|
||||
z-[100], which is what the DS Lens picks too; portals append
|
||||
at the end of <body>, so equal z-index + later DOM order means
|
||||
they'd paint on top of the inversion and skip the flip. Inlined
|
||||
z-index for the same reason the DS does it — Tailwind's JIT
|
||||
scan sometimes drops non-default z utilities. */}
|
||||
<div
|
||||
aria-hidden
|
||||
className="pointer-events-none fixed inset-0"
|
||||
style={{
|
||||
backgroundColor: "var(--foreground)",
|
||||
mixBlendMode: "difference",
|
||||
zIndex: 200,
|
||||
}}
|
||||
/>
|
||||
|
||||
{gpuTier > 0 && (
|
||||
<div
|
||||
aria-hidden
|
||||
className="pointer-events-none fixed inset-0 z-[201]"
|
||||
style={{
|
||||
backgroundImage:
|
||||
"url(\"data:image/svg+xml,%3Csvg viewBox='0 0 512 512' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' fill='%23eaeaea' filter='url(%23n)' opacity='0.6'/%3E%3C/svg%3E\")",
|
||||
backgroundSize: "512px 512px",
|
||||
mixBlendMode: "color-dodge",
|
||||
opacity: "calc(0.55 * var(--noise-opacity-mul, 1))",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -66,7 +66,7 @@ export function ConfirmDialog({
|
|||
onClick={(e) => {
|
||||
if (e.target === e.currentTarget) onCancel();
|
||||
}}
|
||||
className="fixed inset-0 z-[200] flex items-center justify-center bg-background/85 backdrop-blur-sm p-4"
|
||||
className="fixed inset-0 z-[200] flex items-center justify-center bg-background/85 p-4"
|
||||
>
|
||||
<div
|
||||
ref={dialogRef}
|
||||
|
|
|
|||
|
|
@ -288,7 +288,7 @@ export function ModelPickerDialog(props: Props) {
|
|||
// Toast.tsx for the same pattern.
|
||||
return createPortal(
|
||||
<div
|
||||
className="fixed inset-0 z-[100] flex items-center justify-center bg-background/85 backdrop-blur-sm p-4"
|
||||
className="fixed inset-0 z-[100] flex items-center justify-center bg-background/85 p-4"
|
||||
onClick={(e) => e.target === e.currentTarget && onClose()}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
|
|
|
|||
|
|
@ -164,7 +164,7 @@ export function OAuthLoginModal({ provider, onClose, onSuccess }: Props) {
|
|||
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 z-[100] flex items-center justify-center bg-background/85 backdrop-blur-sm p-4"
|
||||
className="fixed inset-0 z-[100] flex items-center justify-center bg-background/85 p-4"
|
||||
onClick={handleBackdrop}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ export function SidebarFooter({ status }: SidebarFooterProps) {
|
|||
"transition-opacity hover:opacity-90",
|
||||
"focus-visible:rounded-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-midground/40",
|
||||
)}
|
||||
style={{ mixBlendMode: "plus-lighter" }}
|
||||
>
|
||||
{t.app.footer.org}
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ export function ThemeSwitcher({ collapsed = false, dropUp = false }: ThemeSwitch
|
|||
aria-label={sheetTitle}
|
||||
className={cn(
|
||||
"min-w-[240px] max-h-[70dvh] overflow-y-auto",
|
||||
"border border-current/20 bg-background-base/95 backdrop-blur-sm",
|
||||
"border border-current/20 bg-background-base/95",
|
||||
"shadow-[0_12px_32px_-8px_rgba(0,0,0,0.6)]",
|
||||
dropUp ? "fixed z-[100]" : "absolute z-50 right-0 top-full mt-1",
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -214,7 +214,7 @@ export function ToolsetConfigDrawer({ toolset, profile, onClose, onChanged }: Pr
|
|||
|
||||
return createPortal(
|
||||
<div
|
||||
className="fixed inset-0 z-[100] flex items-center justify-center bg-background/85 backdrop-blur-sm p-4"
|
||||
className="fixed inset-0 z-[100] flex items-center justify-center bg-background/85 p-4"
|
||||
onMouseDown={(e) => {
|
||||
if (e.target === e.currentTarget) onClose();
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ export function PageHeaderProvider({
|
|||
className={cn(
|
||||
"z-1 w-full shrink-0",
|
||||
"box-border border-b border-current/20",
|
||||
"bg-background-base/40 backdrop-blur-sm",
|
||||
"bg-background-base",
|
||||
// Mobile stacks title + toolbar — fixed h-14 clips content; desktop stays one row.
|
||||
"min-h-0 overflow-x-hidden overflow-y-visible py-3 sm:h-14 sm:min-h-[3.5rem] sm:overflow-hidden sm:py-0",
|
||||
)}
|
||||
|
|
@ -88,7 +88,6 @@ export function PageHeaderProvider({
|
|||
? "shrink truncate"
|
||||
: "truncate",
|
||||
)}
|
||||
style={{ mixBlendMode: "plus-lighter" }}
|
||||
>
|
||||
{displayTitle}
|
||||
</h1>
|
||||
|
|
|
|||
|
|
@ -43,10 +43,8 @@
|
|||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Hermes Agent — Nous DS with the LENS_0 (Hermes teal) lens applied */
|
||||
/* statically. Mirrors nousnet-web/(hermes-agent)/layout.tsx so the */
|
||||
/* canonical Hermes palette is the default — teal canvas + cream */
|
||||
/* accent — without relying on leva/gsap at runtime. */
|
||||
/* Hermes Agent — Nous DS with the LENS_0 (Hermes teal) palette applied
|
||||
statically as the default dashboard theme. */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
:root {
|
||||
|
|
@ -63,10 +61,6 @@
|
|||
--background-base: #041c1c;
|
||||
--background-alpha: 1;
|
||||
|
||||
/* Consumed by <Backdrop />; also theme-switchable. */
|
||||
--warm-glow: rgba(255, 189, 56, 0.35);
|
||||
--noise-opacity-mul: 1;
|
||||
|
||||
/* Typography tokens — rewritten by ThemeProvider. Defaults match the
|
||||
system stack so themes that don't override look native. */
|
||||
--theme-font-sans: system-ui, -apple-system, "Segoe UI", Roboto,
|
||||
|
|
@ -228,11 +222,6 @@ code { font-size: 0.875rem; }
|
|||
display: none;
|
||||
}
|
||||
|
||||
/* Plus-lighter blend used by logos/titles for a subtle glow. */
|
||||
.blend-lighter {
|
||||
mix-blend-mode: plus-lighter;
|
||||
}
|
||||
|
||||
/* System UI-monospace stack — distinct from `font-courier` (Courier
|
||||
Prime), used for dense data readouts where the display font would
|
||||
break the grid. Routes through the theme's mono stack so themes
|
||||
|
|
@ -256,14 +245,3 @@ code { font-size: 0.875rem; }
|
|||
2px 2px;
|
||||
}
|
||||
|
||||
/* When a theme provides `assets.bg`, the backdrop's <div> renders it as
|
||||
a CSS background; the default filler <img> is hidden to prevent
|
||||
double-compositing. Unset → initial → empty, so the :not() selector
|
||||
matches and the default image stays visible. */
|
||||
:root:not([style*="--theme-asset-bg:"]) .theme-default-filler {
|
||||
display: block;
|
||||
}
|
||||
:root[style*="--theme-asset-bg:"] .theme-default-filler {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -326,7 +326,7 @@ export default function ChannelsPage() {
|
|||
{editing && (
|
||||
<div
|
||||
ref={editModalRef}
|
||||
className="fixed inset-0 z-[100] flex items-center justify-center bg-background/85 backdrop-blur-sm p-4"
|
||||
className="fixed inset-0 z-[100] flex items-center justify-center bg-background/85 p-4"
|
||||
onClick={(e) => e.target === e.currentTarget && setEditing(null)}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
|
|
|
|||
|
|
@ -932,7 +932,7 @@ export default function ChatPage({ isActive = true }: { isActive?: boolean }) {
|
|||
onClick={closeMobilePanel}
|
||||
className={cn(
|
||||
"fixed inset-0 z-[55] p-0 block",
|
||||
"bg-black/60 backdrop-blur-sm",
|
||||
"bg-black/60",
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
|
@ -944,7 +944,7 @@ export default function ChatPage({ isActive = true }: { isActive?: boolean }) {
|
|||
className={cn(
|
||||
"font-mondwest fixed top-0 right-0 z-[60] flex h-dvh max-h-dvh w-64 min-w-0 flex-col antialiased",
|
||||
"border-l border-current/20 text-midground",
|
||||
"bg-background-base/95 backdrop-blur-sm",
|
||||
"bg-background-base/95",
|
||||
"transition-transform duration-200 ease-out",
|
||||
"[background:var(--component-sidebar-background)]",
|
||||
"[clip-path:var(--component-sidebar-clip-path)]",
|
||||
|
|
@ -962,7 +962,6 @@ export default function ChatPage({ isActive = true }: { isActive?: boolean }) {
|
|||
<Typography
|
||||
mondwest
|
||||
className="text-display font-bold text-[1.125rem] leading-[0.95] tracking-[0.0525rem] text-midground"
|
||||
style={{ mixBlendMode: "plus-lighter" }}
|
||||
>
|
||||
{t.app.modelToolsSheetTitle}
|
||||
<br />
|
||||
|
|
@ -1037,7 +1036,7 @@ export default function ChatPage({ isActive = true }: { isActive?: boolean }) {
|
|||
Offer an in-place restart so the user never has to refresh the
|
||||
whole page to get a working chat back. */}
|
||||
{sessionEnded && (
|
||||
<div className="absolute inset-0 z-20 flex flex-col items-center justify-center gap-3 bg-black/60 backdrop-blur-sm">
|
||||
<div className="absolute inset-0 z-20 flex flex-col items-center justify-center gap-3 bg-black/60">
|
||||
<div className="text-sm tracking-wide text-white/80">
|
||||
Session ended.
|
||||
</div>
|
||||
|
|
@ -1060,7 +1059,7 @@ export default function ChatPage({ isActive = true }: { isActive?: boolean }) {
|
|||
"absolute z-10",
|
||||
"normal-case tracking-normal font-normal",
|
||||
"rounded border border-current/30",
|
||||
"bg-black/20 backdrop-blur-sm",
|
||||
"bg-black/20",
|
||||
"opacity-70 hover:opacity-100 hover:border-current/60",
|
||||
"transition-opacity duration-150",
|
||||
"bottom-2 right-2 px-2 py-1 text-xs sm:bottom-3 sm:right-3 sm:px-2.5 sm:py-1.5",
|
||||
|
|
|
|||
|
|
@ -814,7 +814,7 @@ export default function CronPage() {
|
|||
{createModalOpen && (
|
||||
<div
|
||||
ref={createModalRef}
|
||||
className="fixed inset-0 z-[100] flex items-center justify-center bg-background/85 backdrop-blur-sm p-4"
|
||||
className="fixed inset-0 z-[100] flex items-center justify-center bg-background/85 p-4"
|
||||
onClick={(e) => e.target === e.currentTarget && setCreateModalOpen(false)}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
|
|
@ -889,7 +889,7 @@ export default function CronPage() {
|
|||
{editJob && (
|
||||
<div
|
||||
ref={editModalRef}
|
||||
className="fixed inset-0 z-[100] flex items-center justify-center bg-background/85 backdrop-blur-sm p-4"
|
||||
className="fixed inset-0 z-[100] flex items-center justify-center bg-background/85 p-4"
|
||||
onClick={(e) => e.target === e.currentTarget && setEditJob(null)}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
|
|
|
|||
|
|
@ -334,7 +334,7 @@ export default function McpPage() {
|
|||
{createModalOpen && (
|
||||
<div
|
||||
ref={createModalRef}
|
||||
className="fixed inset-0 z-[100] flex items-center justify-center bg-background/85 backdrop-blur-sm p-4"
|
||||
className="fixed inset-0 z-[100] flex items-center justify-center bg-background/85 p-4"
|
||||
onClick={(e) =>
|
||||
e.target === e.currentTarget && setCreateModalOpen(false)
|
||||
}
|
||||
|
|
@ -455,7 +455,7 @@ export default function McpPage() {
|
|||
{installEntry && (
|
||||
<div
|
||||
ref={installModalRef}
|
||||
className="fixed inset-0 z-[100] flex items-center justify-center bg-background/85 backdrop-blur-sm p-4"
|
||||
className="fixed inset-0 z-[100] flex items-center justify-center bg-background/85 p-4"
|
||||
onClick={(e) =>
|
||||
e.target === e.currentTarget && setInstallEntry(null)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -575,7 +575,7 @@ function AuxiliaryTasksModal({
|
|||
return (
|
||||
<div
|
||||
ref={modalRef}
|
||||
className="fixed inset-0 z-[100] flex items-center justify-center bg-background/85 backdrop-blur-sm p-4"
|
||||
className="fixed inset-0 z-[100] flex items-center justify-center bg-background/85 p-4"
|
||||
onClick={(e) => e.target === e.currentTarget && onClose()}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
|
|
@ -779,7 +779,7 @@ function MoaModelsModal({
|
|||
if (!preset) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-background/80 p-4 backdrop-blur-sm">
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-background/80 p-4">
|
||||
<Card className="max-h-[85vh] w-full max-w-2xl overflow-auto">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-sm">Configure Mixture of Agents presets</CardTitle>
|
||||
|
|
|
|||
|
|
@ -804,7 +804,7 @@ export default function ProfilesPage() {
|
|||
{createModalOpen && (
|
||||
<div
|
||||
ref={createModalRef}
|
||||
className="fixed inset-0 z-[100] flex items-center justify-center bg-background/85 backdrop-blur-sm p-4"
|
||||
className="fixed inset-0 z-[100] flex items-center justify-center bg-background/85 p-4"
|
||||
onClick={(e) =>
|
||||
e.target === e.currentTarget && setCreateModalOpen(false)
|
||||
}
|
||||
|
|
@ -1231,7 +1231,7 @@ export default function ProfilesPage() {
|
|||
{editorName && (
|
||||
<div
|
||||
ref={editorModalRef}
|
||||
className="fixed inset-0 z-[100] flex items-center justify-center bg-background/85 backdrop-blur-sm p-4"
|
||||
className="fixed inset-0 z-[100] flex items-center justify-center bg-background/85 p-4"
|
||||
onClick={(e) => e.target === e.currentTarget && closeEditor()}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
|
|
|
|||
|
|
@ -684,7 +684,7 @@ export default function SystemPage() {
|
|||
{hookModalOpen && (
|
||||
<div
|
||||
ref={hookModalRef}
|
||||
className="fixed inset-0 z-[100] flex items-center justify-center bg-background/85 backdrop-blur-sm p-4"
|
||||
className="fixed inset-0 z-[100] flex items-center justify-center bg-background/85 p-4"
|
||||
onClick={(e) => e.target === e.currentTarget && setHookModalOpen(false)}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
|
|
|
|||
|
|
@ -303,7 +303,7 @@ export default function WebhooksPage() {
|
|||
{createModalOpen && (
|
||||
<div
|
||||
ref={createModalRef}
|
||||
className="fixed inset-0 z-[100] flex items-center justify-center bg-background/85 backdrop-blur-sm p-4"
|
||||
className="fixed inset-0 z-[100] flex items-center justify-center bg-background/85 p-4"
|
||||
onClick={(e) => e.target === e.currentTarget && closeCreateModal()}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import React, { Fragment, useEffect, useState } from "react";
|
|||
* these in their manifest's `slots` field get wired in automatically.
|
||||
*
|
||||
* Shell-wide slots:
|
||||
* - `backdrop` — rendered inside `<Backdrop />`, above the noise layer
|
||||
* - `backdrop` — optional full-viewport background decoration
|
||||
* - `header-left` — injected before the Hermes brand in the top bar
|
||||
* - `header-right` — injected before the theme/language switchers
|
||||
* - `header-banner` — injected below the top nav bar, full-width
|
||||
|
|
|
|||
|
|
@ -184,98 +184,26 @@ export const roseTheme: DashboardTheme = {
|
|||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Nous Blue — the inverted "light mode" Hermes look, ported from the
|
||||
* LENS_5I overlay preset in `@nous-research/ui`.
|
||||
*
|
||||
* Unlike the other built-ins (which paint dark color directly on the
|
||||
* canvas), this theme relies on `<Backdrop />`'s foreground inversion
|
||||
* layer: an opaque white sheet at z-200 with `mix-blend-mode: difference`
|
||||
* that flips the entire stack below it. Authoring colors stay dark
|
||||
* (`#170d02` brown background, `#FFAC02` orange midground), and the
|
||||
* inversion converts them to their visual complements at paint time —
|
||||
* the orange midground reads as #0053FD Nous-blue on screen, against a
|
||||
* cream `#E8F2FD` canvas.
|
||||
*
|
||||
* Note on bg blend mode: the DS Lens uses `multiply` for LENS_5I because
|
||||
* nousnet-web's <body> is white; hermes-agent's App root is `bg-black`,
|
||||
* so we leave the bg layer's blend mode at the `difference` default —
|
||||
* `difference(#170d02, #000)` passes the bg through unchanged, and the
|
||||
* subsequent FG-difference layer then inverts it to cream. Using
|
||||
* `multiply` here would collapse the bg to pure black against the
|
||||
* `bg-black` root and produce a plain-white canvas instead of the
|
||||
* intended cream-blue.
|
||||
*
|
||||
* Source of truth for the palette: `design-language/src/ui/components/
|
||||
* overlays/lens.ts` (LENS_5I export).
|
||||
*/
|
||||
/** Light mode — vivid Nous-blue accents on a cream canvas. */
|
||||
export const nousBlueTheme: DashboardTheme = {
|
||||
name: "nous-blue",
|
||||
label: "Nous Blue",
|
||||
description: "Light mode — vivid Nous-blue accents on cream canvas",
|
||||
palette: {
|
||||
background: { hex: "#170d02", alpha: 1 },
|
||||
midground: { hex: "#FFAC02", alpha: 1 },
|
||||
foreground: { hex: "#FFFFFF", alpha: 1 },
|
||||
// Same warm-amber as nousnet-web's overlay glow; after the FG
|
||||
// inversion it reads as a cool ultraviolet vignette in the top-left.
|
||||
warmGlow: "rgba(255, 172, 2, 0.18)",
|
||||
// Noise sits above the FG inversion and is NOT flipped, so a softer
|
||||
// multiplier keeps it from speckling over the bright post-inversion
|
||||
// canvas.
|
||||
noiseOpacity: 0.4,
|
||||
background: { hex: "#E8F2FD", alpha: 1 },
|
||||
midground: { hex: "#0053FD", alpha: 1 },
|
||||
foreground: { hex: "#170d02", alpha: 0 },
|
||||
warmGlow: "rgba(0, 83, 253, 0.12)",
|
||||
noiseOpacity: 0,
|
||||
},
|
||||
typography: DEFAULT_TYPOGRAPHY,
|
||||
layout: DEFAULT_LAYOUT,
|
||||
// Inverted page: the embedded terminal is below the FG layer too, so
|
||||
// a `#000000` source paints as visual white — i.e. a proper light-mode
|
||||
// terminal pane. xterm picks lighter palette colors against the "black"
|
||||
// canvas, which then read as dark text on screen post-inversion.
|
||||
terminalBackground: "#000000",
|
||||
componentStyles: {
|
||||
backdrop: {
|
||||
// Lower than LENS_5I.Lens.fillerOpacity (0.06). The filler texture
|
||||
// gets amplified post-inversion: small variations against the deep
|
||||
// `#170d02` source bg are barely visible, but those same variations
|
||||
// against the bright `#E8F2FD` post-inversion canvas read as a
|
||||
// heavy cloud/marble pattern — especially on near-empty pages
|
||||
// (loading spinners, blank states). 0.02 keeps subtle grain
|
||||
// without overwhelming the canvas.
|
||||
fillerOpacity: "0.02",
|
||||
},
|
||||
},
|
||||
// Pre-invert absolute-hex tokens so they read as their familiar colors
|
||||
// through the FG difference layer. e.g. source #04D3C9 (cyan) is what
|
||||
// gets painted, and `255 - channel` flips it to #FB2C36 (red) on screen.
|
||||
// Without these, the default destructive/success/warning tokens would
|
||||
// appear as their unintuitive complements.
|
||||
colorOverrides: {
|
||||
destructive: "#04d3c9",
|
||||
destructiveForeground: "#000000",
|
||||
success: "#b5217f",
|
||||
warning: "#0042c7",
|
||||
},
|
||||
// Pre-inverted data-series accents for the Analytics/Models token
|
||||
// charts. The defaults (#ffe6cb cream + #34d399 emerald) would render
|
||||
// through the FG difference layer as dark navy + hot-coral on the
|
||||
// bright Nous-blue canvas — the coral is the "red" users see for
|
||||
// Output values without these overrides. Source → on-screen:
|
||||
// Input: #ffe6cb → #001934 (dark navy) ← unchanged
|
||||
// Output: #ffac02 → #0053fd (vivid Nous-blue) ← brand accent
|
||||
// Input keeps the cream source so it stays a neutral, low-contrast
|
||||
// dark-blue against the cream canvas; output paints as the brand
|
||||
// Nous-blue so the "primary" series in token-flow charts reads as
|
||||
// the highlight color, matching the rest of the inverted UI chrome.
|
||||
terminalBackground: "#f5f8fc",
|
||||
seriesColors: {
|
||||
inputTokenAccent: "#ffe6cb",
|
||||
outputTokenAccent: "#ffac02",
|
||||
inputTokenAccent: "#001934",
|
||||
outputTokenAccent: "#0053fd",
|
||||
},
|
||||
// Explicit picker swatch — the raw palette hex (`#170d02`, `#FFAC02`,
|
||||
// amber rgba) doesn't reflect what users see after the FG inversion,
|
||||
// so we paint the post-inversion visual triplet directly:
|
||||
// white → vivid Nous-blue → cream/light-blue
|
||||
// matching the actual on-screen rendering of the theme.
|
||||
swatchColors: ["#FFFFFF", "#0053FD", "#E8F2FD"],
|
||||
swatchColors: ["#170d02", "#0053FD", "#E8F2FD"],
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -4,10 +4,9 @@
|
|||
* Themes customise three orthogonal layers:
|
||||
*
|
||||
* 1. `palette` — the 3-layer color triplet (background/midground/
|
||||
* foreground) + warm-glow + noise opacity. The
|
||||
* design-system cascade in `src/index.css` derives
|
||||
* every shadcn-compat token (card, muted, border,
|
||||
* primary, etc.) from this triplet via `color-mix()`.
|
||||
* foreground). Legacy `warmGlow` / `noiseOpacity`
|
||||
* fields remain for theme YAML compat but are unused
|
||||
* by the lightweight shell.
|
||||
* 2. `typography` — font families, base font size, line height,
|
||||
* letter spacing. An optional `fontUrl` is injected
|
||||
* as `<link rel="stylesheet">` so self-hosted and
|
||||
|
|
@ -33,10 +32,9 @@ export interface ThemePalette {
|
|||
/** Top-layer highlight. In LENS_0 this is white @ alpha 0 — invisible by
|
||||
* default but still drives `--color-ring`-style accents. */
|
||||
foreground: ThemeLayer;
|
||||
/** Warm vignette color for <Backdrop />, as an rgba() string. */
|
||||
/** Legacy palette field — kept for theme YAML compat. */
|
||||
warmGlow: string;
|
||||
/** Scalar multiplier (0–1.2) on the noise overlay. Lower for softer themes
|
||||
* like Mono and Rosé, higher for grittier themes like Cyberpunk. */
|
||||
/** Legacy palette field — kept for theme YAML compat. */
|
||||
noiseOpacity: number;
|
||||
}
|
||||
|
||||
|
|
@ -79,12 +77,10 @@ export interface ThemeLayout {
|
|||
export type ThemeLayoutVariant = "standard" | "cockpit" | "tiled";
|
||||
|
||||
/** Named hero/background assets a theme can populate. Each value is
|
||||
* emitted as a CSS var (`--theme-asset-<name>`). The default shell
|
||||
* consumes `bg` in `<Backdrop />` when present; other slots are
|
||||
* plugin-facing — a cockpit sidebar plugin reads `--theme-asset-hero`
|
||||
* to render its hero render without coupling to the theme name. */
|
||||
* emitted as a CSS var (`--theme-asset-<name>`). Plugin slots and
|
||||
* shell chrome may consume these via CSS. */
|
||||
export interface ThemeAssets {
|
||||
/** Full-viewport background image URL, injected under the noise layer. */
|
||||
/** Full-viewport background image URL. */
|
||||
bg?: string;
|
||||
/** Hero render (Gundam, mascot, wallpaper) — for plugin sidebars/overlays. */
|
||||
hero?: string;
|
||||
|
|
@ -103,7 +99,7 @@ export interface ThemeAssets {
|
|||
|
||||
/** Component-style override buckets. Each bucket's entries become CSS
|
||||
* vars (`--component-<bucket>-<kebab-property>`) that shell components
|
||||
* (Card, Backdrop, App header/footer, etc.) read. Values are plain CSS
|
||||
* (Card, App header/footer, etc.) read. Values are plain CSS
|
||||
* strings — we don't parse them, so themes can use `clip-path`,
|
||||
* `border-image`, `background`, `box-shadow`, and anything else CSS
|
||||
* accepts. */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue