One border weight, and no sliver beside the nav

The nav's right edge read as two lines because it was two things: its own
border, and a sliver of the stage's background showing through a
sub-pixel gap before the app's webview began. The stage rect is now
rounded to whole pixels and to the same edges each time, so the app
covers the stage exactly.

Borders are one weight throughout and the lightest that still separates -
slate-200 in light, slate-800 in dark. Nothing drawn heavier than it
needs to be to read as an edge.
This commit is contained in:
2026-09-01 15:32:21 +02:00
parent 0567a4b7c7
commit 07bb22eb2d
4 changed files with 25 additions and 9 deletions
+7 -1
View File
@@ -56,7 +56,13 @@ export default function App() {
const el = stageRef.current;
if (!el) return;
const r = el.getBoundingClientRect();
void api.setStage(r.x, r.y, r.width, r.height, 0);
/* Rounded to whole pixels, and to the same edges each time. A fractional
left edge leaves a sliver of the stage's own background showing between
the nav's border and the app — which reads as a second, lighter border
line running down the whole window. */
const x = Math.round(r.x);
const y = Math.round(r.y);
void api.setStage(x, y, Math.round(r.right) - x, Math.round(r.bottom) - y, 0);
}, []);
useLayoutEffect(() => {
+1 -1
View File
@@ -38,7 +38,7 @@ export default function Nav({
const disabled = !activeId;
const shell =
"flex shrink-0 flex-col overflow-hidden border-r border-slate-300 bg-white " +
"flex shrink-0 flex-col overflow-hidden border-r border-slate-200 bg-white " +
"dark:border-slate-800 dark:bg-slate-900";
/* Back, forward, reload and settings. Nothing hides an element here — that
+7 -7
View File
@@ -25,8 +25,8 @@ export const SECTION = "border-b border-slate-200 px-4 py-4 dark:border-slate-80
/* No width: callers set it. A `w-full` baked in here loses the specificity
coin-toss against a `w-[110px]` or a `flex-1` at the call site. */
export const INPUT =
`${CONTROL_H} min-w-0 rounded-lg border border-slate-300 bg-white px-3 text-[12px] outline-none ` +
"placeholder:text-slate-400 dark:border-slate-700 dark:bg-slate-800 dark:placeholder:text-slate-500";
`${CONTROL_H} min-w-0 rounded-lg border border-slate-200 bg-white px-3 text-[12px] outline-none ` +
"placeholder:text-slate-400 dark:border-slate-800 dark:bg-slate-800 dark:placeholder:text-slate-500";
export const SUBPANEL = "rounded-lg bg-slate-50 p-3 dark:bg-slate-800/50";
const BTN_BASE =
@@ -34,9 +34,9 @@ const BTN_BASE =
"disabled:cursor-not-allowed cursor-pointer";
export const BTN =
`${BTN_BASE} border border-slate-300 px-2.5 font-medium ` +
`${BTN_BASE} border border-slate-200 px-2.5 font-medium ` +
"hover:border-sky-500 hover:text-sky-600 disabled:opacity-40 " +
"dark:border-slate-700 dark:hover:border-sky-500 dark:hover:text-sky-400";
"dark:border-slate-800 dark:hover:border-sky-500 dark:hover:text-sky-400";
export const BTN_PRIMARY =
`${BTN_BASE} bg-sky-500 px-3 font-semibold text-white hover:bg-sky-400 disabled:opacity-40`;
@@ -86,7 +86,7 @@ export function Segmented<T extends string>({
return (
<div
role="group"
className={`flex ${CONTROL_H} items-center rounded-lg border border-slate-300 p-0.5 dark:border-slate-700`}
className={`flex ${CONTROL_H} items-center rounded-lg border border-slate-200 p-0.5 dark:border-slate-800`}
>
{options.map((o) => {
const active = o.value === value;
@@ -173,8 +173,8 @@ export function Dialog({
wide
? "max-w-[min(1120px,92vw)] max-h-[90vh] overflow-y-auto"
: "max-w-sm"
} rounded-2xl border border-slate-300 bg-white p-5 shadow-2xl
dark:border-slate-700 dark:bg-slate-900`}
} rounded-2xl border border-slate-200 bg-white p-5 shadow-2xl
dark:border-slate-800 dark:bg-slate-900`}
>
<h2 className="mb-1.5 text-[15px] font-semibold tracking-tight">{title}</h2>
<div className="mb-4 text-[13px] leading-relaxed text-slate-600 dark:text-slate-300">