diff --git a/frontend/packages/swarm-ui/src/shell/Shell.css b/frontend/packages/swarm-ui/src/shell/Shell.css index aff85761..6eafd5c1 100644 --- a/frontend/packages/swarm-ui/src/shell/Shell.css +++ b/frontend/packages/swarm-ui/src/shell/Shell.css @@ -30,8 +30,28 @@ } .shell-brand { font-weight: 600; + /* Base colour only — Shell.tsx overrides it inline to the same + currently-active-tab accent `.shell-nav-indicator` uses (falling + back to this `--purple` before the first measurement lands, which + is also the "hives" default route's own accent, so there's no + visible mismatch at first paint). Transition lives here, same + 140ms hop cadence + the same three-rule motion guard as the + indicator, so this and the indicator read as one thing changing, + not two independently-synced ones. */ color: var(--purple); letter-spacing: 0.02em; + transition: color 140ms ease; +} +@media (prefers-reduced-motion: reduce) { + .shell-brand { + transition: none; + } +} +:root[data-motion='reduce'] .shell-brand { + transition: none; +} +:root[data-motion='allow'] .shell-brand { + transition: color 140ms ease; } .shell-nav { display: flex; diff --git a/frontend/packages/swarm-ui/src/shell/Shell.tsx b/frontend/packages/swarm-ui/src/shell/Shell.tsx index c24eada0..6117eae3 100644 --- a/frontend/packages/swarm-ui/src/shell/Shell.tsx +++ b/frontend/packages/swarm-ui/src/shell/Shell.tsx @@ -24,6 +24,8 @@ // through the existing base16 chromatic slots (`NAV_ITEMS`' `accent` // field), not a continuous hue rotation — everything here still // derives from base16, same rule the rest of the palette follows. +// `.shell-brand` rides the identical accent value (`navAccent` below), +// so the header reads as one accent changing, not the underline alone. import { useEffect, useRef, useState } from 'preact/hooks'; import type { ComponentChildren } from 'preact'; import { Link, useLocation } from 'wouter-preact'; @@ -218,10 +220,18 @@ export function Shell({ children }: { children: ComponentChildren }) { document.title = brand; }, [brand]); + // Same value the indicator itself is drawn with, including mid-sweep — + // the brand text rides the identical hop sequence rather than + // computing its own, so "the header's accent" reads as one thing + // changing, not two things that happen to agree at rest. + const navAccent = indicator?.accent ?? 'var(--purple)'; + return (