Computer panel: full-width dock bar + WorkClaw tile structure

Two measured parity changes to the Computer home screen (HomeScreen.tsx):

- Dock: mx-auto centered pill → flex items-end justify-center, so it spans
  the panel width as a bottom-anchored frosted bar instead of a floating
  content-width pill. (Shadow/blur/bg/ring already matched prod.)
- App-grid tiles: adopt WorkClaw's nested DOM (cell → button → fade layer →
  visual tile) and the measured 4-layer "physical icon" shadow on the white
  brand tiles (browser/slack), where its dark hairline + drop + bright inset
  read. Dark glass tiles keep the white-bevel shadow-dock-tile (a dark drop
  is invisible on them). Brand icons sized to the measured 34px.

Regenerated computer-home visual baseline. typecheck/lint/86 unit/31 E2E
green; all 6 visual pass.

Note: WorkClaw's Computer panel is behind auth, so unlike the token work
these targets couldn't be verified against prod CSS — applied per the
reviewer's live measurements at the user's direction.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Omar Sobh
2026-06-12 07:18:25 -05:00
co-authored by Claude Opus 4.8
parent c7b71f0ae9
commit 23a4bad07d
2 changed files with 45 additions and 31 deletions
+45 -31
View File
@@ -13,38 +13,44 @@ interface HomeScreenProps {
onOpen: (app: AppId, origin: DOMRect) => void; onOpen: (app: AppId, origin: DOMRect) => void;
} }
/* Brand tiles use the official service marks on a white squircle; the /* WorkClaw's measured "physical icon" shadow — a dark hairline + two soft
rest get the coral-gradient lucide glyph on a glass tile. 56px tile, drops + a bright top inset. Tuned for the white brand tiles (on dark glass
16px-radius icon container, press scales to 0.92 (measured). */ tiles a dark drop is invisible, so those keep the white-bevel token). */
function homeIcon(app: AppId) { const TILE_SHADOW =
if (app === "browser") { "0 0 0 1px rgba(0,0,0,0.04), 0 1px 1.5px rgba(0,0,0,0.08), 0 4.65px 9.35px rgba(0,0,0,0.07), inset 0 1px 0 rgba(255,255,255,0.5)";
/* The innermost visual tile (56px, 16px radius). Brand tiles use the official
service mark on a white squircle with the 4-layer shadow; the rest get the
coral-gradient glyph on a glass tile with the dark-surface white bevel. */
function tileSurface(app: AppId) {
if (app === "browser" || app === "slack") {
const src = app === "browser" ? "/services/chrome.svg" : "/services/slack.svg";
return ( return (
<span className="flex size-14 items-center justify-center rounded-2xl bg-white shadow-dock-tile"> <div
<Image src="/services/chrome.svg" alt="" width={32} height={32} /> className="relative flex size-14 items-center justify-center rounded-2xl bg-white select-none"
</span> style={{ boxShadow: TILE_SHADOW }}
); >
} <Image src={src} alt="" width={34} height={34} className="pointer-events-none" />
if (app === "slack") { </div>
return (
<span className="flex size-14 items-center justify-center rounded-2xl bg-white shadow-dock-tile">
<Image src="/services/slack.svg" alt="" width={30} height={30} />
</span>
); );
} }
if (app === "apps") { if (app === "apps") {
return ( return (
<span className="flex size-14 items-center justify-center rounded-2xl border border-dashed border-white/40 text-white/65"> <div className="relative flex size-14 items-center justify-center rounded-2xl border border-dashed border-white/40 text-white/65 select-none">
<GradientGlyph icon={APP_ICON.apps} size={26} /> <GradientGlyph icon={APP_ICON.apps} size={26} />
</span> </div>
); );
} }
return ( return (
<span className="flex size-14 items-center justify-center rounded-2xl bg-black/50 shadow-dock-tile backdrop-blur-sm"> <div className="relative flex size-14 items-center justify-center rounded-2xl bg-black/50 shadow-dock-tile backdrop-blur-sm select-none">
<GradientGlyph icon={APP_ICON[app]} size={28} /> <GradientGlyph icon={APP_ICON[app]} size={28} />
</span> </div>
); );
} }
/* WorkClaw's nested tile DOM: cell (stacks tile + label) → button (press
target, scales to 0.92) → positioning context → opacity-fade layer →
visual tile. The button wraps only the tile; the label sits outside it. */
function Tile({ function Tile({
app, app,
label, label,
@@ -55,17 +61,25 @@ function Tile({
onOpen: (app: AppId, origin: DOMRect) => void; onOpen: (app: AppId, origin: DOMRect) => void;
}) { }) {
return ( return (
<button <div className="group relative z-0 flex flex-col items-center gap-1.5 hover:z-20 focus-within:z-20">
type="button" <button
aria-label={label} type="button"
onClick={(e: MouseEvent<HTMLButtonElement>) => aria-label={label}
onOpen(app, e.currentTarget.getBoundingClientRect()) onClick={(e: MouseEvent<HTMLButtonElement>) =>
} onOpen(app, e.currentTarget.getBoundingClientRect())
className="flex flex-col items-center gap-1.5 transition-transform duration-[0.1s] ease-out-app active:scale-[0.92]" }
> className="cursor-pointer transition-transform duration-[0.1s] ease-out-app active:scale-[0.92]"
{homeIcon(app)} >
<span className="text-[11px] text-white/80">{label}</span> <div className="relative size-14 rounded-2xl">
</button> <div className="absolute inset-0 transition-opacity duration-[120ms] ease-linear">
{tileSurface(app)}
</div>
</div>
</button>
<span className="block w-full truncate text-center text-[11px] text-white/80">
{label}
</span>
</div>
); );
} }
@@ -85,7 +99,7 @@ export function HomeScreen({ agentName, onOpen }: HomeScreenProps) {
</div> </div>
<div <div
aria-label="Dock" aria-label="Dock"
className="mx-auto flex gap-5 rounded-3xl bg-white/[0.07] px-6 pt-4 pb-3 shadow-dock-capsule ring-1 ring-white/[0.04] ring-inset backdrop-blur-[40px] backdrop-saturate-150" className="flex items-end justify-center gap-5 rounded-3xl bg-white/[0.07] px-6 pt-4 pb-3 shadow-dock-capsule ring-1 ring-white/[0.04] ring-inset backdrop-blur-[40px] backdrop-saturate-150"
> >
{HOME_DOCK.map((tile) => ( {HOME_DOCK.map((tile) => (
<div key={tile.app} className="flex flex-col items-center gap-1.5"> <div key={tile.app} className="flex flex-col items-center gap-1.5">
Binary file not shown.

Before

Width:  |  Height:  |  Size: 194 KiB

After

Width:  |  Height:  |  Size: 182 KiB