feat(ui): refine gem-blue acrylic palette and focus borders

Derive the gem-blue palette with OKLCH tonal and complementary-color
rules while preserving acrylic opacity, blur and surface hierarchy.

Darker focus tokens exposed stacked field rings, fallback outlines and
persistent basemap selection rings. Keep fallback focus styles in the
base layer and use one themed field boundary or inset card stroke.
Preserve keyboard focus, invalid states and native input semantics.

Add browser regression coverage for pointer and keyboard focus, all
basemap presentations, narrow layouts and rendered contrast.

Validation in an isolated checkout: pnpm lint, pnpm typecheck,
pnpm test (393 tests), focused browser suites and pnpm build.
This commit is contained in:
2026-10-09 10:57:06 +08:00
parent d2d705ed4c
commit 617ba4d87c
69 changed files with 1239 additions and 765 deletions
@@ -45,12 +45,12 @@ export function MapControlPanel({
)}
>
<div className={cn("relative mb-3 flex items-center gap-2.5 px-2.5 py-2", MAP_READABLE_RADIUS_CLASS_NAME, MAP_READABLE_SURFACE_CLASS_NAME)}>
<span className={cn("grid h-8 w-8 shrink-0 place-items-center bg-blue-100 text-blue-700", MAP_ICON_CELL_RADIUS_CLASS_NAME)}>
<span className={cn("grid h-8 w-8 shrink-0 place-items-center bg-[var(--action-selection-soft)] text-[var(--action-blue-hover)]", MAP_ICON_CELL_RADIUS_CLASS_NAME)}>
<Icon size={17} aria-hidden="true" />
</span>
<div className="min-w-0">
<h2 className="truncate text-sm font-semibold leading-tight text-slate-950">{title}</h2>
{description ? <p className="mt-0.5 truncate text-xs text-slate-500">{description}</p> : null}
{description ? <p className="mt-0.5 truncate text-xs text-[var(--ink-muted)]">{description}</p> : null}
</div>
</div>
<div className={cn("relative space-y-3", contentClassName)}>{children}</div>
@@ -61,7 +61,7 @@ export function MapControlPanel({
export function MapPanelSection({ title, children }: { title: string; children: ReactNode }) {
return (
<section>
<div className="mb-1.5 px-1 text-xs font-semibold text-slate-500">{title}</div>
<div className="mb-1.5 px-1 text-xs font-semibold text-[var(--ink-muted)]">{title}</div>
<div className="space-y-1.5">{children}</div>
</section>
);
@@ -85,7 +85,7 @@ export function MapModeButton({ icon: Icon, label, active = false, disabled = fa
className={cn(
"flex h-14 flex-col items-center justify-center gap-1 text-xs font-semibold transition duration-150",
MAP_COMPACT_RADIUS_CLASS_NAME,
active ? "bg-blue-600 text-white" : "surface-control text-slate-600 hover:text-blue-700",
active ? "bg-[var(--action-blue)] text-white" : "surface-control text-slate-600 hover:text-[var(--action-blue-hover)]",
disabled && "cursor-not-allowed opacity-55 hover:text-slate-600"
)}
>
@@ -112,7 +112,7 @@ export function MapActionChip({
disabled={disabled}
onClick={onClick}
className={cn(
"flex h-10 items-center justify-center gap-1.5 text-xs font-semibold text-slate-700 transition duration-150 hover:border-blue-200 hover:text-blue-700",
"flex h-10 items-center justify-center gap-1.5 text-xs font-semibold text-slate-700 transition duration-150 hover:border-[var(--surface-border)] hover:text-[var(--action-blue-hover)]",
MAP_COMPACT_RADIUS_CLASS_NAME,
MAP_READABLE_SURFACE_CLASS_NAME,
disabled && "cursor-not-allowed opacity-55 hover:border-white/70 hover:text-slate-700"
@@ -163,17 +163,17 @@ export function MapActionRow({
"flex min-h-12 w-full items-center gap-2.5 border px-2.5 py-2 text-left",
MAP_COMPACT_RADIUS_CLASS_NAME,
isPrimary
? "border-blue-600 bg-blue-600 text-white shadow-lg shadow-blue-600/25 hover:bg-blue-700"
? "border-[var(--action-blue)] bg-[var(--action-blue)] text-white shadow-lg shadow-[color:var(--action-blue)]/25 hover:bg-[var(--action-blue-hover)]"
: strong
? "border-blue-100 bg-blue-50/95 text-blue-800"
: "surface-control border-transparent text-slate-700 hover:border-blue-100 hover:bg-white hover:text-blue-700",
? "border-[var(--surface-border)] bg-[var(--action-soft)]/95 text-[var(--action-blue-pressed)]"
: "surface-control border-transparent text-slate-700 hover:border-[var(--surface-border)] hover:bg-[var(--surface-reading)] hover:text-[var(--action-blue-hover)]",
isDanger && "hover:border-red-100 hover:text-red-700",
selected && !isPrimary && "border-blue-200 bg-blue-50 text-blue-800",
selected && !isPrimary && "border-[var(--surface-border)] bg-[var(--action-soft)] text-[var(--action-blue-pressed)]",
muted && "opacity-70",
disabled &&
(isPrimary
? "cursor-not-allowed opacity-55 hover:border-blue-600 hover:bg-blue-600 hover:text-white"
: "cursor-not-allowed opacity-55 hover:border-white/70 hover:bg-white/95 hover:text-slate-700"),
? "cursor-not-allowed opacity-55 hover:border-[var(--action-blue)] hover:bg-[var(--action-blue-hover)] hover:text-white"
: "cursor-not-allowed opacity-55 hover:border-white/70 hover:bg-[var(--surface-reading)]/95 hover:text-slate-700"),
disabled && "shadow-none"
)}
>
@@ -182,10 +182,10 @@ export function MapActionRow({
"grid h-8 w-8 shrink-0 place-items-center",
MAP_ICON_CELL_RADIUS_CLASS_NAME,
isPrimary
? "bg-white/16 text-white"
? "bg-[var(--surface-reading)]/16 text-white"
: selected || strong
? "bg-blue-600 text-white"
: "bg-slate-100 text-slate-500",
? "bg-[var(--action-blue)] text-white"
: "bg-[var(--surface-control)] text-[var(--ink-muted)]",
isDanger && "bg-red-50 text-red-600"
)}
>
@@ -193,19 +193,19 @@ export function MapActionRow({
</span>
<span className="min-w-0 flex-1">
<span className="block truncate text-sm font-semibold">{label}</span>
<span className={cn("block truncate text-xs", isPrimary ? "text-blue-100" : "text-slate-500")}>{description}</span>
<span className={cn("block truncate text-xs", isPrimary ? "text-[var(--palette-paper)]" : "text-[var(--ink-muted)]")}>{description}</span>
</span>
{status ? (
<span
className={cn(
"shrink-0 rounded-full px-2 py-0.5 text-xs font-semibold",
isPrimary ? "bg-white/15 text-white" : "bg-slate-100 text-slate-500"
isPrimary ? "bg-[var(--surface-reading)]/15 text-white" : "bg-[var(--surface-control)] text-[var(--ink-muted)]"
)}
>
{status}
</span>
) : (
<ChevronRight size={15} aria-hidden="true" className={cn("shrink-0", isPrimary ? "text-blue-100" : "text-slate-400")} />
<ChevronRight size={15} aria-hidden="true" className={cn("shrink-0", isPrimary ? "text-[var(--palette-paper)]" : "text-[var(--ink-muted)]")} />
)}
</button>
);
@@ -214,7 +214,7 @@ export function MapActionRow({
export function MapMetricTile({ label, value }: { label: string; value: string }) {
return (
<div className={cn("px-2.5 py-2", MAP_READABLE_RADIUS_CLASS_NAME, MAP_READABLE_SURFACE_STRONG_CLASS_NAME)}>
<div className="text-xs text-slate-500">{label}</div>
<div className="text-xs text-[var(--ink-muted)]">{label}</div>
<div className="mt-1 truncate text-sm font-semibold text-slate-900">{value}</div>
</div>
);