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:
co-authored by
Claude Opus 4.8
parent
c7b71f0ae9
commit
23a4bad07d
@@ -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 (
|
||||||
|
<div className="group relative z-0 flex flex-col items-center gap-1.5 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="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)}
|
<div className="relative size-14 rounded-2xl">
|
||||||
<span className="text-[11px] text-white/80">{label}</span>
|
<div className="absolute inset-0 transition-opacity duration-[120ms] ease-linear">
|
||||||
|
{tileSurface(app)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</button>
|
</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 |
Reference in New Issue
Block a user