Computer panel: match measured WorkClaw geometry (from capture)

Forensic capture of the live app's Computer panel gave exact ground truth,
correcting my prior commit (which trusted a reviewer's measurements):

- Dock is content-width (~380px) centered by a w-full wrapper, NOT stretched
  full-width — my last change had wrongly stretched it. Reverts to the real
  centered-pill behaviour.
- Dark tiles (#1f1f1f) gain the measured grounding drop in --shadow-dock-tile
  (0 4.65px 9.35px rgba(0,0,0,0.2)); previously flat (hairline + inset only).
- AppIcon cells fixed to 68px / 8px gap; grid gap 34px×16px; icons 34px; the
  dock now uses the same nested tile component as the grid.
- White brand-tile shadow + tile nesting were already exact (capture confirms).

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

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Omar Sobh
2026-06-12 08:00:16 -05:00
co-authored by Claude Opus 4.8
parent 355a6f23b9
commit 36087c30f5
3 changed files with 34 additions and 41 deletions
+2 -1
View File
@@ -174,7 +174,8 @@
--shadow-popover: 0 16px 40px -12px #0009; --shadow-popover: 0 16px 40px -12px #0009;
--shadow-dialog: 0 32px 80px -24px #000000b3; --shadow-dialog: 0 32px 80px -24px #000000b3;
--shadow-edge: -3px 0 14px -4px #0006; /* panel left edge */ --shadow-edge: -3px 0 14px -4px #0006; /* panel left edge */
--shadow-dock-tile: 0 0 0 1px #ffffff14, inset 0 1px 0 #ffffff2e; --shadow-dock-tile: 0 0 0 1px #ffffff14, 0 4.65px 9.35px #0003,
inset 0 1px 0 #ffffff2e; /* white hairline + grounding drop + top inset (WorkClaw) */
--shadow-cta: inset 0 1px 0 #ffffff40, 0 3px 10px -3px #f965658c; /* coral CTA */ --shadow-cta: inset 0 1px 0 #ffffff40, 0 3px 10px -3px #f965658c; /* coral CTA */
--shadow-launcher: inset 0 1px 0 #ffffff38, 0 5px 14px -3px #f965657a; /* header Computer launcher */ --shadow-launcher: inset 0 1px 0 #ffffff38, 0 5px 14px -3px #f965657a; /* header Computer launcher */
--shadow-screen: --shadow-screen:
+26 -34
View File
@@ -13,15 +13,16 @@ interface HomeScreenProps {
onOpen: (app: AppId, origin: DOMRect) => void; onOpen: (app: AppId, origin: DOMRect) => void;
} }
/* WorkClaw's measured "physical icon" shadow — a dark hairline + two soft /* WorkClaw's measured white-tile "physical icon" shadow (verified from prod):
drops + a bright top inset. Tuned for the white brand tiles (on dark glass dark hairline + two soft drops + a bright top inset. Reads on the white
tiles a dark drop is invisible, so those keep the white-bevel token). */ brand tiles. Dark tiles use --shadow-dock-tile (white hairline + grounding
drop + top inset). */
const TILE_SHADOW = const TILE_SHADOW =
"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)"; "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 /* 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 service mark on a white squircle; the rest get the coral-gradient glyph on a
coral-gradient glyph on a glass tile with the dark-surface white bevel. */ #1f1f1f tile. Icons are 34px (measured). */
function tileSurface(app: AppId) { function tileSurface(app: AppId) {
if (app === "browser" || app === "slack") { if (app === "browser" || app === "slack") {
const src = app === "browser" ? "/services/chrome.svg" : "/services/slack.svg"; const src = app === "browser" ? "/services/chrome.svg" : "/services/slack.svg";
@@ -42,15 +43,16 @@ function tileSurface(app: AppId) {
); );
} }
return ( return (
<div className="relative flex size-14 items-center justify-center rounded-2xl bg-black/50 shadow-dock-tile backdrop-blur-sm select-none"> <div className="relative flex size-14 items-center justify-center rounded-2xl bg-[#1f1f1f] shadow-dock-tile select-none">
<GradientGlyph icon={APP_ICON[app]} size={28} /> <GradientGlyph icon={APP_ICON[app]} size={34} />
</div> </div>
); );
} }
/* WorkClaw's nested tile DOM: cell (stacks tile + label) → button (press /* WorkClaw's AppIcon: a 68px cell (8px gap to label) → button (press target,
target, scales to 0.92) → positioning context → opacity-fade layer → scales to 0.92) → positioning context → opacity-fade layer → visual tile.
visual tile. The button wraps only the tile; the label sits outside it. */ The button wraps only the tile; the label sits outside it. Used for both the
grid and the dock. */
function Tile({ function Tile({
app, app,
label, label,
@@ -61,14 +63,14 @@ function Tile({
onOpen: (app: AppId, origin: DOMRect) => void; onOpen: (app: AppId, origin: DOMRect) => void;
}) { }) {
return ( return (
<div className="group relative z-0 flex flex-col items-center gap-1.5 hover:z-20 focus-within:z-20"> <div className="group relative z-0 flex w-[68px] flex-col items-center gap-2 hover:z-20 focus-within:z-20">
<button <button
type="button" type="button"
aria-label={label} aria-label={label}
onClick={(e: MouseEvent<HTMLButtonElement>) => onClick={(e: MouseEvent<HTMLButtonElement>) =>
onOpen(app, e.currentTarget.getBoundingClientRect()) onOpen(app, e.currentTarget.getBoundingClientRect())
} }
className="cursor-pointer transition-transform duration-[0.1s] ease-out-app active:scale-[0.92]" className="cursor-pointer transition-transform duration-100 ease-out active:scale-[0.92]"
> >
<div className="relative size-14 rounded-2xl"> <div className="relative size-14 rounded-2xl">
<div className="absolute inset-0 transition-opacity duration-[120ms] ease-linear"> <div className="absolute inset-0 transition-opacity duration-[120ms] ease-linear">
@@ -83,40 +85,30 @@ function Tile({
); );
} }
/** The measured home screen: app grid over the wallpaper, frosted dock. */ /** The measured home screen: app grid over the wallpaper, frosted dock. The
* dock is content-width (≈380px) and centered by its w-full wrapper — not
* stretched (verified against prod). */
export function HomeScreen({ agentName, onOpen }: HomeScreenProps) { export function HomeScreen({ agentName, onOpen }: HomeScreenProps) {
return ( return (
<div className="relative flex flex-1 flex-col justify-between p-5"> <div className="relative flex flex-1 flex-col justify-between pt-5">
<div className="grid grid-cols-4 gap-3" aria-label={`${agentName}'s apps`}> <div
className="grid grid-cols-4 gap-x-4 gap-y-[34px] px-3"
aria-label={`${agentName}'s apps`}
>
{HOME_GRID.map((tile) => ( {HOME_GRID.map((tile) => (
<Tile <Tile key={tile.app} app={tile.app} label={tile.label} onOpen={onOpen} />
key={tile.app}
app={tile.app}
label={tile.label}
onOpen={onOpen}
/>
))} ))}
</div> </div>
<div className="flex w-full items-end justify-center px-3 pb-8">
<div <div
aria-label="Dock" aria-label="Dock"
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" 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"> <Tile key={tile.app} app={tile.app} label={tile.label} onOpen={onOpen} />
<button
type="button"
aria-label={tile.label}
onClick={(e) =>
onOpen(tile.app, e.currentTarget.getBoundingClientRect())
}
className="flex size-14 items-center justify-center rounded-2xl bg-black shadow-dock-tile transition-transform duration-[0.1s] ease-out-app active:scale-[0.92]"
>
<GradientGlyph icon={APP_ICON[tile.app]} size={34} />
</button>
<span className="text-[11px] text-white/80">{tile.label}</span>
</div>
))} ))}
</div> </div>
</div> </div>
</div>
); );
} }
Binary file not shown.

Before

Width:  |  Height:  |  Size: 182 KiB

After

Width:  |  Height:  |  Size: 195 KiB