Makeover sweep across the tools screens: glass panels, pills, chips

- Hardware: every panel is a glass card with an icon disc header, metric
  rows with the hint under the value, and more room between cards.
- Notifications: unread rows are raised glass with a coral dot instead of a
  solid accent tint; the unread bar matches.
- Settings: text size and theme pickers are chips/glass pills; Backup
  presets and Profile's Crop/Fit are chips; the roster's Save/Add are pills.
- Captures: the count sits beside the description.
- Changelog/FAQ: a wrapped bullet stays one list item (was split into a
  stray paragraph).
- Sweep of my screens, components and layout: old rounded-md/lg bordered
  boxes become glass panels or radius tokens, small buttons become pills,
  single-line fields are glass pills; undefined colour tokens
  (--color-error/-danger/-surface-1) and Tailwind palette colours replaced
  with theme tokens.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
This commit is contained in:
phantomptrandClaude Opus 5.5 committed 2026-10-10 11:03:21 -07:00
1 parent b5f5e6dfc1
commit b41f76240a
47 files changed
+268 -220

No files matched your search

+1 -1
View File
@@ -115,7 +115,7 @@ export function Drawer({
type="button"
onClick={onClose}
aria-label={tr("close", "Close")}
className="shrink-0 rounded p-1 text-[var(--color-muted)] hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text)]"
className="shrink-0 rounded-full p-1.5 text-[var(--color-muted)] hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text)]"
>
<X size={16} aria-hidden="true" />
</button>
+1 -1
View File
@@ -84,7 +84,7 @@ export default function EditSessionBanner({
};
return (
<div className="flex flex-col gap-2 rounded-md border border-[var(--color-accent)] bg-[var(--color-accent-soft)] p-3">
<div className="flex flex-col gap-2 rounded-[var(--radius-card)] border border-[var(--color-accent)] bg-[var(--color-accent-soft)] p-3">
<div className="flex flex-wrap items-center gap-3">
<FilePenLine
size={18}
+3 -3
View File
@@ -116,14 +116,14 @@ export class RootErrorBoundary extends Component<Props, State> {
onClick={() =>
this.setState({ err: null, componentStack: "" })
}
className="rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] px-3 py-1.5 text-sm font-medium hover:bg-[var(--color-surface-3)]"
className="chip min-h-9 px-4 text-sm font-medium"
>
{this.tr("errorboundary_try_again", "Try again")}
</button>
<button
type="button"
onClick={() => window.location.reload()}
className="rounded-md bg-[var(--color-bad)] px-3 py-1.5 text-sm font-medium text-[var(--color-accent-contrast)] hover:opacity-90"
className="rounded-full border border-[color-mix(in_srgb,var(--color-bad)_45%,transparent)] bg-[var(--color-surface-raised)] px-4 py-1.5 text-sm font-medium text-[var(--color-bad)] hover:bg-[var(--color-bad-soft)]"
>
{this.tr("errorboundary_reload_window", "Reload window")}
</button>
@@ -145,7 +145,7 @@ export class RootErrorBoundary extends Component<Props, State> {
}
});
}}
className="rounded-md border border-[var(--color-accent)] px-3 py-1.5 text-sm font-medium text-[var(--color-accent)] hover:bg-[var(--color-surface)]"
className="chip min-h-9 px-4 text-sm font-medium text-[var(--color-accent)]"
>
{this.tr("errorboundary_report_crash", "Report this crash")}
</button>
+2 -2
View File
@@ -111,7 +111,7 @@ export function PickerView(p: PickerViewProps) {
<button
type="button"
aria-label={tr("cancel", undefined, "Cancel")}
className="rounded p-1 text-[var(--color-muted)] hover:bg-[var(--color-surface)]"
className="rounded-full p-1.5 text-[var(--color-muted)] hover:bg-[var(--color-surface)]"
onClick={p.onCancel}
>
<X size={18} />
@@ -156,7 +156,7 @@ export function PickerView(p: PickerViewProps) {
<span className="flex-1 truncate text-xs text-[var(--color-muted)]">{p.cwdLabel}</span>
{!p.remote && p.roots.length > 1 && (
<select
className="max-w-[40%] truncate rounded border border-[var(--color-border)] bg-[var(--color-surface)] px-1 py-0.5 text-xs"
className="max-w-[40%] truncate rounded-full border border-[var(--glass-edge)] bg-[var(--color-surface-raised)] px-1 py-0.5 text-xs"
value={p.currentRoot ?? ""}
onChange={(e) => e.target.value && p.onRoot(e.target.value)}
>
@@ -47,3 +47,27 @@ describe("MarkdownView tables", () => {
expect((out.match(/<li[ >]/g) ?? []).length).toBe(2);
});
});
describe("MarkdownView lists", () => {
it("keeps a wrapped bullet's indented lines in the same item", () => {
const out = html(
[
"- **The limit was too high.** It held each connection to",
" the limit instead of the whole transfer.",
"- Second item",
"",
"After the list.",
].join("\n"),
);
expect((out.match(/<li[ >]/g) ?? []).length).toBe(2);
expect(out).toContain("connection to the limit instead");
// The continuation is not a paragraph of its own.
expect((out.match(/<p[ >]/g) ?? []).length).toBe(1);
});
it("joins wrapped lines in numbered lists too", () => {
const out = html(["1. First step that", " wraps", "2. Second"].join("\n"));
expect((out.match(/<li[ >]/g) ?? []).length).toBe(2);
expect(out).toContain("First step that wraps");
});
});
+18 -1
View File
@@ -170,6 +170,12 @@ function parseBlocks(md: string): Block[] {
while (i < lines.length && /^\s*[-*+]\s+/.test(lines[i])) {
items.push(lines[i].replace(/^\s*[-*+]\s+/, ""));
i += 1;
// An indented line under an item continues it (a wrapped bullet in
// CHANGELOG.md); it isn't a new paragraph.
while (i < lines.length && isContinuation(lines[i])) {
items[items.length - 1] += " " + lines[i].trim();
i += 1;
}
}
blocks.push({ kind: "ul", content: "", items });
continue;
@@ -179,6 +185,12 @@ function parseBlocks(md: string): Block[] {
while (i < lines.length && /^\s*\d+\.\s+/.test(lines[i])) {
items.push(lines[i].replace(/^\s*\d+\.\s+/, ""));
i += 1;
// An indented line under an item continues it (a wrapped bullet in
// CHANGELOG.md); it isn't a new paragraph.
while (i < lines.length && isContinuation(lines[i])) {
items[items.length - 1] += " " + lines[i].trim();
i += 1;
}
}
blocks.push({ kind: "ol", content: "", items });
continue;
@@ -206,6 +218,11 @@ function parseBlocks(md: string): Block[] {
return blocks;
}
/** A list item's wrapped line: indented, not blank, not a new item. */
function isContinuation(line: string): boolean {
return /^\s{2,}\S/.test(line) && !/^\s*([-*+]|\d+\.)\s+/.test(line);
}
export function MarkdownView({ source }: { source: string }) {
const blocks = useMemo(() => parseBlocks(source), [source]);
return (
@@ -333,7 +350,7 @@ export function MarkdownView({ source }: { source: string }) {
return (
<pre
key={key}
className="mb-5 overflow-x-auto rounded-md border border-[var(--color-border)] bg-[var(--color-surface-2)] p-4 font-mono text-[13px] leading-relaxed shadow-sm"
className="mb-5 overflow-x-auto rounded-[var(--radius-card)] border border-[var(--color-border)] bg-[var(--color-surface-2)] p-4 font-mono text-[13px] leading-relaxed shadow-sm"
>
<code>{b.content}</code>
</pre>
+2 -2
View File
@@ -222,7 +222,7 @@ export function PackagePanelView({
return (
<div className="grid gap-4 p-4">
<header className="relative overflow-hidden rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)] p-4">
<header className="relative overflow-hidden rounded-[var(--radius-card)] border border-[var(--color-border)] bg-[var(--color-surface-2)] p-4">
{backdropUrl && (
<img
src={backdropUrl}
@@ -335,7 +335,7 @@ export function PackagePanelView({
{notes && (
<div className="text-sm">
<div className="mb-1 text-[var(--color-muted)]">{tr("viewer_whats_new", undefined, "What's new")}</div>
<p className="whitespace-pre-wrap rounded-md bg-[var(--color-surface-2)] p-3 text-xs">
<p className="whitespace-pre-wrap rounded-[var(--radius-field)] bg-[var(--color-surface-2)] p-3 text-xs">
{notes}
</p>
</div>
+1 -1
View File
@@ -411,7 +411,7 @@ export function TaskList({ maxFinished = 20 }: { maxFinished?: number }) {
<button
type="button"
onClick={clearFinished}
className="ml-auto rounded-md border border-[var(--color-border)] px-2 py-0.5 text-xs normal-case tracking-normal hover:bg-[var(--color-surface-3)]"
className="ml-auto rounded-full border border-[var(--color-border)] px-2 py-0.5 text-xs normal-case tracking-normal hover:bg-[var(--color-surface-3)]"
>
{tr("task_clear_finished", undefined, "Clear finished")}
</button>
+1 -1
View File
@@ -70,7 +70,7 @@ function ToastItem({ toast }: { toast: Toast }) {
type="button"
onClick={() => dismiss(toast.id)}
aria-label={tr("dismiss", "Dismiss")}
className="shrink-0 rounded p-0.5 text-[var(--color-muted)] hover:bg-[var(--color-surface-3)]"
className="shrink-0 rounded-full p-1 text-[var(--color-muted)] hover:bg-[var(--color-surface-3)]"
>
<X size={14} aria-hidden="true" />
</button>
@@ -32,7 +32,7 @@ export function FilesListView(props: {
value={props.query}
onChange={(e) => props.onQuery(e.target.value)}
placeholder={tr("viewer_files_search", undefined, "Search files")}
className="rounded-md border border-[var(--color-border)] bg-[var(--color-surface-2)] px-3 py-1.5 text-sm"
className="rounded-full border border-[var(--glass-edge)] bg-[var(--color-surface-raised)] px-3 py-1.5 text-sm"
/>
<div className="text-xs text-[var(--color-muted)]">
{tr(
@@ -22,7 +22,7 @@ function ImageCard({ token, name, size }: { token: string; name: string; size: n
};
}, [token, name]);
return (
<figure className="grid gap-1.5 rounded-md border border-[var(--color-border)] p-2">
<figure className="grid gap-1.5 rounded-[var(--radius-card)] border border-[var(--color-border)] p-3">
<div className="flex aspect-video items-center justify-center overflow-hidden rounded bg-[var(--color-surface-2)]">
{url ? (
<img src={url} alt={name} loading="lazy" className="max-h-full max-w-full object-contain" />
@@ -32,7 +32,7 @@ export function RelatedTab(props: {
{g.items.map((i) => (
<li
key={`${i.where}:${i.path ?? i.name}`}
className="flex items-center gap-2 rounded-md border border-[var(--color-border)] px-3 py-2 text-sm"
className="flex items-center gap-2 rounded-[var(--radius-card)] border border-[var(--color-border)] px-3 py-2 text-sm"
>
<span className="min-w-0 flex-1 truncate">{i.name}</span>
{i.version && <span className="font-mono text-xs text-[var(--color-muted)]">{i.version}</span>}
+13
View File
@@ -463,6 +463,19 @@ body {
border-bottom: 0;
}
/* A panel header's icon: a small coral-on-blush disc, the same language as
* the Home tiles' icon buttons. */
.icon-disc {
display: inline-grid;
width: 2rem;
height: 2rem;
flex: none;
place-items: center;
border-radius: 9999px;
background: var(--color-accent-soft);
color: var(--color-accent-bright);
}
/* Chip: a pill filter. Active = white filled, inactive = hairline outline.
* Colour and shape only: size comes from the caller's utilities (this sheet
* is unlayered, so anything set here would beat them). */
+3 -3
View File
@@ -163,7 +163,7 @@ export default function NotificationInbox({
<Bell size={variant === "orb" ? 18 : 14} />
{unread > 0 && (
<span
className="absolute -right-0.5 -top-0.5 inline-flex h-4 min-w-4 items-center justify-center rounded-full bg-[var(--color-bad)] px-1 text-[10px] font-semibold tabular-nums text-white"
className="absolute -right-0.5 -top-0.5 inline-flex h-4 min-w-4 items-center justify-center rounded-full bg-[var(--color-bad)] px-1 text-[10px] font-semibold tabular-nums text-[var(--color-accent-contrast)]"
title={tr(
unread === 1
? "notifications_unread_one"
@@ -214,7 +214,7 @@ export default function NotificationInbox({
type="button"
onClick={clear}
aria-label={tr("notifications_clear", undefined, "Clear all")}
className="rounded p-1 text-[var(--color-muted)] hover:bg-[var(--color-surface-2)] hover:text-[var(--color-text)]"
className="rounded-full p-1.5 text-[var(--color-muted)] hover:bg-[var(--color-surface-2)] hover:text-[var(--color-text)]"
title={tr("notifications_clear", undefined, "Clear all")}
>
<Trash2 size={12} />
@@ -225,7 +225,7 @@ export default function NotificationInbox({
onClick={() => setOpen(false)}
aria-label={tr("close", undefined, "Close")}
title={tr("close", undefined, "Close")}
className="rounded p-1 text-[var(--color-muted)] hover:bg-[var(--color-surface-2)] hover:text-[var(--color-text)]"
className="rounded-full p-1.5 text-[var(--color-muted)] hover:bg-[var(--color-surface-2)] hover:text-[var(--color-text)]"
>
<X size={12} />
</button>
+11 -11
View File
@@ -251,14 +251,14 @@ function RosterManageModal({ onClose }: { onClose: () => void }) {
value={editName}
onChange={(e) => setEditName(e.target.value)}
placeholder={tr("roster_edit_name_placeholder", "Name")}
className="flex-1 rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] px-2 py-1"
className="flex-1 rounded-full border border-[var(--glass-edge)] bg-[var(--color-surface-raised)] px-2 py-1"
/>
<input
value={editHost}
onChange={(e) => setEditHost(e.target.value)}
placeholder="192.168.1.50"
inputMode="decimal"
className="flex-1 rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] px-2 py-1"
className="flex-1 rounded-full border border-[var(--glass-edge)] bg-[var(--color-surface-raised)] px-2 py-1"
/>
</div>
<textarea
@@ -271,13 +271,13 @@ function RosterManageModal({ onClose }: { onClose: () => void }) {
undefined,
"Notes (e.g. firmware quirks, do-not-update warnings)",
)}
className="w-full resize-y rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] px-2 py-1 text-xs"
className="w-full resize-y rounded-[var(--radius-field)] border border-[var(--glass-edge)] bg-[var(--color-surface-raised)] px-2 py-1 text-xs"
/>
<div className="flex items-center gap-2">
<button
type="button"
onClick={commitEdit}
className="rounded-md bg-[var(--color-accent)] px-2 py-1 text-[var(--color-accent-contrast)]"
className="inline-flex items-center gap-1 rounded-full bg-[image:var(--accent-fill)] px-3 py-1 text-xs font-semibold text-[var(--color-accent-contrast)] shadow-[var(--accent-glow)] hover:brightness-105 disabled:opacity-50"
>
{tr("save", undefined, "Save")}
</button>
@@ -319,7 +319,7 @@ function RosterManageModal({ onClose }: { onClose: () => void }) {
onClick={() => startEdit(p)}
aria-label={tr("roster_edit", undefined, "Edit PS5")}
title={tr("roster_edit", undefined, "Edit PS5")}
className="rounded-md p-1 text-[var(--color-muted)] hover:bg-[var(--color-surface)] hover:text-[var(--color-text)]"
className="rounded-full p-1.5 text-[var(--color-muted)] hover:bg-[var(--color-surface)] hover:text-[var(--color-text)]"
>
<Pencil size={12} />
</button>
@@ -333,7 +333,7 @@ function RosterManageModal({ onClose }: { onClose: () => void }) {
`Move ${p.name} up`,
)}
title={tr("roster_move_up", undefined, "Move up")}
className="rounded-md p-1 text-[var(--color-muted)] hover:bg-[var(--color-surface)] hover:text-[var(--color-text)] disabled:cursor-not-allowed disabled:opacity-30"
className="rounded-full p-1.5 text-[var(--color-muted)] hover:bg-[var(--color-surface)] hover:text-[var(--color-text)] disabled:cursor-not-allowed disabled:opacity-30"
>
<ChevronUp size={12} />
</button>
@@ -347,7 +347,7 @@ function RosterManageModal({ onClose }: { onClose: () => void }) {
`Move ${p.name} down`,
)}
title={tr("roster_move_down", undefined, "Move down")}
className="rounded-md p-1 text-[var(--color-muted)] hover:bg-[var(--color-surface)] hover:text-[var(--color-text)] disabled:cursor-not-allowed disabled:opacity-30"
className="rounded-full p-1.5 text-[var(--color-muted)] hover:bg-[var(--color-surface)] hover:text-[var(--color-text)] disabled:cursor-not-allowed disabled:opacity-30"
>
<ChevronDownIcon size={12} />
</button>
@@ -376,7 +376,7 @@ function RosterManageModal({ onClose }: { onClose: () => void }) {
`Remove ${p.name}`,
)}
title={tr("remove", undefined, "Remove")}
className="rounded-md p-1 text-[var(--color-muted)] hover:bg-[var(--color-bad-soft)] hover:text-[var(--color-bad)]"
className="rounded-full p-1.5 text-[var(--color-muted)] hover:bg-[var(--color-bad-soft)] hover:text-[var(--color-bad)]"
>
<Trash2 size={12} />
</button>
@@ -399,20 +399,20 @@ function RosterManageModal({ onClose }: { onClose: () => void }) {
undefined,
"Living-room PS5",
)}
className="flex-1 rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] px-2 py-1 text-xs"
className="flex-1 rounded-full border border-[var(--glass-edge)] bg-[var(--color-surface-raised)] px-2 py-1 text-xs"
/>
<input
value={newHost}
onChange={(e) => setNewHost(e.target.value)}
placeholder="192.168.1.50"
inputMode="decimal"
className="flex-1 rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] px-2 py-1 text-xs"
className="flex-1 rounded-full border border-[var(--glass-edge)] bg-[var(--color-surface-raised)] px-2 py-1 text-xs"
/>
<button
type="button"
onClick={handleAdd}
disabled={!newHost.trim()}
className="inline-flex items-center gap-1 rounded-md bg-[var(--color-accent)] px-2 py-1 text-xs text-[var(--color-accent-contrast)] disabled:opacity-50"
className="inline-flex items-center gap-1 rounded-full bg-[image:var(--accent-fill)] px-3 py-1 text-xs font-semibold text-[var(--color-accent-contrast)] shadow-[var(--accent-glow)] hover:brightness-105 disabled:opacity-50"
>
<Plus size={11} />
{tr("add", undefined, "Add")}
+2 -2
View File
@@ -78,7 +78,7 @@ export function UpdateToast() {
type="button"
disabled={downloading}
onClick={() => void download()}
className="inline-flex shrink-0 items-center gap-1.5 rounded-md border border-[var(--color-accent)] px-2.5 py-1 text-xs font-medium text-[var(--color-accent)] disabled:opacity-60"
className="inline-flex shrink-0 items-center gap-1.5 rounded-full border border-[var(--color-accent)] px-2.5 py-1 text-xs font-medium text-[var(--color-accent)] disabled:opacity-60"
>
{downloading ? (
<Spinner size={14} />
@@ -96,7 +96,7 @@ export function UpdateToast() {
type="button"
aria-label={tr("update_toast_dismiss", undefined, "Dismiss")}
onClick={() => setDismissedVersion(version)}
className="shrink-0 rounded p-1 text-[var(--color-muted)] hover:text-[var(--color-text)]"
className="shrink-0 rounded-full p-1.5 text-[var(--color-muted)] hover:text-[var(--color-text)]"
>
<X size={14} />
</button>
+1 -1
View File
@@ -248,7 +248,7 @@ function FeatureTile({
body: string;
}) {
return (
<div className="flex flex-col gap-2 rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)] p-4">
<div className="surface-panel flex flex-col gap-2 p-5">
<Icon size={18} className="text-[var(--color-accent)]" />
<div className="text-sm font-semibold">{title}</div>
<div className="text-xs leading-relaxed text-[var(--color-muted)]">
+6 -6
View File
@@ -73,7 +73,7 @@ export function StatsPanel({ entries }: { entries: ActivityEntry[] }) {
</Button>
</div>
{entries.length === 0 ? (
<div className="rounded-md border border-dashed border-[var(--color-border)] p-6 text-center text-xs text-[var(--color-muted)]">
<div className="rounded-[var(--radius-card)] border border-dashed border-[var(--color-border)] p-6 text-center text-xs text-[var(--color-muted)]">
{tr(
"stats_empty",
undefined,
@@ -143,7 +143,7 @@ function KpiCard({
sub?: string;
}) {
return (
<div className="rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)] p-3">
<div className="rounded-[var(--radius-card)] border border-[var(--color-border)] bg-[var(--color-surface-2)] p-3">
<div className="flex items-center gap-1.5 text-xs font-semibold uppercase tracking-wide text-[var(--color-muted)]">
{icon}
{label}
@@ -166,7 +166,7 @@ function DailyChart({
const tr = useTr();
const maxCount = Math.max(...days.map((d) => d.count), 1);
return (
<section className="rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)] p-4">
<section className="surface-panel p-5">
<h3 className="mb-3 text-sm font-semibold">
{tr("stats_daily", undefined, "Operations per day (last 30)")}
</h3>
@@ -180,7 +180,7 @@ function DailyChart({
title={`${d.date}: ${d.count} operations · ${formatBytes(d.bytes)}`}
>
<div
className="rounded-t bg-[var(--color-accent)] transition-all hover:opacity-80"
className="rounded-t-md bg-[image:var(--accent-fill)] transition-all hover:opacity-80"
style={{
height: `${Math.max(heightPct, 2)}%`,
minHeight: d.count > 0 ? "2px" : "0",
@@ -209,7 +209,7 @@ function KindBreakdown({
const tr = useTr();
const sorted = Object.entries(counts).sort((a, b) => b[1] - a[1]);
return (
<section className="rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)] p-4">
<section className="surface-panel p-5">
<h3 className="mb-3 text-sm font-semibold">
{tr("stats_breakdown", undefined, "Operation breakdown")}
</h3>
@@ -245,7 +245,7 @@ function TopTransfers({
}) {
const tr = useTr();
return (
<section className="rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)] p-4">
<section className="surface-panel p-5">
<h3 className="mb-3 text-sm font-semibold">
{tr("stats_top_transfers", undefined, "Fastest transfers")}
</h3>
+5 -5
View File
@@ -419,7 +419,7 @@ function ActivityRow({ entry }: { entry: ActivityEntry }) {
<button
type="button"
onClick={() => setDetailOpen(true)}
className="rounded-md border border-[var(--color-border)] p-1 text-[var(--color-muted)] hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text)]"
className="rounded-full border border-[var(--color-border)] p-1 text-[var(--color-muted)] hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text)]"
title={tr("activity_view_tooltip", undefined, "View details")}
>
<Eye size={13} />
@@ -429,7 +429,7 @@ function ActivityRow({ entry }: { entry: ActivityEntry }) {
<button
type="button"
onClick={() => void handleStop()}
className="rounded-md border border-[var(--color-border)] px-2 py-0.5 text-xs hover:bg-[var(--color-surface-3)]"
className="chip min-h-7 px-3 text-xs"
title={
stopAction === "cancel"
? tr(
@@ -453,7 +453,7 @@ function ActivityRow({ entry }: { entry: ActivityEntry }) {
<button
type="button"
onClick={() => remove(entry.id)}
className="rounded-md border border-[var(--color-border)] p-1 text-[var(--color-muted)] hover:bg-[var(--color-surface-3)] hover:text-[var(--color-bad)]"
className="rounded-full border border-[var(--color-border)] p-1 text-[var(--color-muted)] hover:bg-[var(--color-surface-3)] hover:text-[var(--color-bad)]"
title={tr(
"activity_delete_tooltip",
undefined,
@@ -697,7 +697,7 @@ function ActivityDetailModal({
))}
</dl>
{entry.error && (
<div className="mt-3 rounded-md border border-[var(--color-border)] bg-[var(--color-surface-2)] p-2 text-xs">
<div className="mt-3 rounded-[var(--radius-card)] border border-[var(--color-border)] bg-[var(--color-surface-2)] p-3 text-xs">
<div className="mb-1 font-medium text-[var(--color-bad)]">
{tr("activity_detail_error", undefined, "Error")}
</div>
@@ -793,7 +793,7 @@ function ActivityTimeline({ entries }: { entries: ActivityEntry[] }) {
}
return (
<section className="rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)] p-4">
<section className="surface-panel p-5">
<header className="mb-3 flex items-center gap-2">
<h3 className="text-sm font-semibold">
{tr(
+5 -8
View File
@@ -249,7 +249,7 @@ export default function BackupScreen() {
{error && <div className="mb-4"><ErrorCard title={error} onRetry={() => void refresh()} onDismiss={() => setError(null)} /></div>}
{actionMsg && (
<div className="rounded-lg border border-[var(--color-good)]/30 bg-[var(--color-good)]/10 px-4 py-3 text-sm text-[var(--color-good)]">
<div className="rounded-[var(--radius-card)] border border-[var(--color-good)]/30 bg-[var(--color-good)]/10 px-4 py-3 text-sm text-[var(--color-good)]">
{actionMsg}
</div>
)}
@@ -283,11 +283,8 @@ export default function BackupScreen() {
setTag(defaultTagFor(preset.path));
}}
title={preset.path}
className={`rounded-md border px-2 py-1 text-xs ${
path === preset.path
? "border-[var(--color-accent)] bg-[var(--color-accent)]/10"
: "border-[var(--color-border)] hover:bg-[var(--color-surface)]"
}`}
aria-pressed={path === preset.path}
className="chip min-h-8 px-3.5 text-xs max-md:min-h-11"
>
{tr(preset.labelKey, undefined, preset.label)}
</button>
@@ -356,7 +353,7 @@ export default function BackupScreen() {
{entries.map((entry) => (
<div
key={`${entry.tag}-${entry.timestamp}`}
className="flex items-center justify-between rounded-md border border-[var(--color-border)] bg-[var(--color-surface-2)] px-3 py-2.5"
className="flex items-center justify-between rounded-[var(--radius-card)] border border-[var(--color-border)] bg-[var(--color-surface-2)] px-3 py-2.5"
>
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
@@ -409,7 +406,7 @@ export default function BackupScreen() {
</div>
)}
<div className="flex items-start gap-2 rounded-lg border border-[var(--color-warn)]/20 bg-[var(--color-warn)]/5 px-4 py-3 text-xs text-[var(--color-warn)]">
<div className="flex items-start gap-2 rounded-[var(--radius-card)] border border-[var(--color-warn)]/20 bg-[var(--color-warn)]/5 px-4 py-3 text-xs text-[var(--color-warn)]">
<AlertTriangle size={14} className="mt-0.5 shrink-0" />
<div className="space-y-1">
<p>
+1 -1
View File
@@ -46,7 +46,7 @@ export default function LogsSection({
value={draft.customStart ? toLocalInput(draft.customStart) : ""}
max={latest}
onChange={(e) => update({ customStart: e.target.value ? new Date(e.target.value).getTime() : null })}
className="rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] px-3 py-2 text-sm font-normal text-[var(--color-text)]"
className="rounded-full border border-[var(--glass-edge)] bg-[var(--color-surface-raised)] px-3 py-2 text-sm font-normal text-[var(--color-text)]"
/>
</label>
)}
@@ -118,7 +118,7 @@ export default function ScreenshotsSection({
aria-label={s.name}
onClick={() => toggle(s)}
className={
"relative block w-full rounded-lg border-2 p-0.5 transition-colors " +
"relative block w-full rounded-[var(--radius-field)] border-2 p-0.5 transition-colors " +
(on
? "border-[var(--color-accent)]"
: "border-transparent opacity-75 hover:border-[var(--color-border-strong)] hover:opacity-100")
@@ -130,7 +130,7 @@ export default function ScreenshotsSection({
"absolute right-1.5 top-1.5 flex h-5 w-5 items-center justify-center rounded-full border " +
(on
? "border-[var(--color-accent)] bg-[var(--color-accent)] text-[var(--color-accent-contrast)]"
: "border-white/70 bg-black/40")
: "border-[var(--glass-edge)] bg-[var(--color-float)]")
}
>
{on && <Check size={12} strokeWidth={3} />}
@@ -154,7 +154,7 @@ export default function ScreenshotsSection({
type="button"
aria-label={`${tr("br_remove", undefined, "Remove")} ${f.name}`}
onClick={() => setAttached((a) => ({ ...a, files: a.files.filter((x) => x !== f) }))}
className="absolute right-1.5 top-1.5 rounded-full bg-black/70 p-1 text-white"
className="glass-float absolute right-1.5 top-1.5 rounded-full p-1 text-[var(--color-text)]"
>
<X size={12} />
</button>
+1 -1
View File
@@ -96,7 +96,7 @@ export default function SendPanel({
value={discordFallback}
rows={8}
onFocus={(e) => e.currentTarget.select()}
className="w-full rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] p-2 font-mono text-xs"
className="w-full rounded-[var(--radius-field)] border border-[var(--glass-edge)] bg-[var(--color-surface-raised)] p-2 font-mono text-xs"
/>
<div>
<Button size="sm" variant="secondary" leftIcon={<Copy size={12} />} onClick={() => void writeClipboard(discordFallback)}>
+2 -2
View File
@@ -38,14 +38,14 @@ export default function WhatSection({
{f.pinned.map((e) => (
<div
key={`${e.src}-${e.ts}-${e.code}`}
className="flex items-start gap-2 rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] px-3 py-2"
className="flex items-start gap-2 rounded-[var(--radius-card)] border border-[var(--color-border)] bg-[var(--color-surface)] px-3 py-2"
>
<span className="min-w-0 flex-1 break-words font-mono text-xs">{formatLine(e)}</span>
<button
type="button"
aria-label={tr("br_remove", undefined, "Remove")}
onClick={() => updateForm({ pinned: f.pinned.filter((p) => p !== e) })}
className="shrink-0 rounded p-0.5 text-[var(--color-muted)] hover:text-[var(--color-text)]"
className="shrink-0 rounded-full p-1 text-[var(--color-muted)] hover:text-[var(--color-text)]"
>
<X size={14} />
</button>
@@ -21,14 +21,14 @@ export function CaptureToolbar({
}) {
return (
<div className="mb-4 flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
<p className="flex min-w-0 max-w-3xl flex-wrap items-center gap-2 text-sm leading-relaxed text-[var(--color-muted)]">
<p className="flex min-w-0 max-w-3xl items-start gap-2.5 text-sm leading-relaxed text-[var(--color-muted)]">
{count !== undefined && (
<span className="shrink-0 rounded-full bg-[var(--color-surface-3)] px-2 py-0.5 text-xs tabular-nums">
<span className="mt-0.5 shrink-0 rounded-full border border-[var(--glass-edge)] bg-[var(--color-surface-raised)] px-2.5 py-0.5 text-xs font-semibold tabular-nums text-[var(--color-text)] shadow-[var(--edge-highlight)]">
{count}
</span>
)}
{loading && <Spinner size={14} tone="accent" />}
<span className="min-w-0">{description}</span>
{loading && <Spinner size={14} tone="accent" className="mt-1 shrink-0" />}
<span className="min-w-0 flex-1">{description}</span>
</p>
{right && <div className="flex shrink-0 flex-wrap items-center gap-2">{right}</div>}
</div>
@@ -191,7 +191,7 @@ export function CheatGameDetail({
</button>
{/* Who the game is. */}
<div className="flex flex-col gap-4 rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)] p-4 sm:flex-row">
<div className="flex flex-col gap-4 rounded-[var(--radius-card)] border border-[var(--color-border)] bg-[var(--color-surface-2)] p-4 sm:flex-row">
<GameIcon host={hostOf(host)} titleId={game.titleId} size={112} rounded="rounded-lg" />
<div className="min-w-0 flex-1">
<div className="flex flex-wrap items-center gap-2">
@@ -230,7 +230,7 @@ export function CheatGameDetail({
{/* Step: switch cheats on. Only once some are on the console. */}
{game.downloaded && (
<section className="rounded-lg border border-[var(--color-border)] p-4">
<section className="surface-panel p-5">
<div className="mb-3 flex flex-wrap items-center justify-between gap-2">
<div>
<h3 className="text-sm font-semibold">
@@ -312,7 +312,7 @@ export function CheatGameDetail({
)}
{/* Step: get cheats. */}
<section className="rounded-lg border border-[var(--color-border)] p-4">
<section className="surface-panel p-5">
<h3 className="text-sm font-semibold">
{game.downloaded
? tr("cheats_more_title", undefined, "More cheats for this game")
+1 -1
View File
@@ -258,7 +258,7 @@ export default function CheatsScreen() {
].map((text, i) => (
<li
key={i}
className="flex items-start gap-2 rounded-md border border-[var(--color-border)] bg-[var(--color-surface-2)] px-3 py-2"
className="flex items-start gap-2 rounded-[var(--radius-card)] border border-[var(--color-border)] bg-[var(--color-surface-2)] px-3 py-2"
>
<span className="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-[var(--color-accent)] text-[11px] font-semibold text-[var(--color-accent-contrast)]">
{i + 1}
+4 -4
View File
@@ -77,7 +77,7 @@ function Question({
return (
<li
id={item.id}
className="rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)]"
className="surface-panel"
data-testid="faq-item"
>
<button
@@ -237,7 +237,7 @@ export default function FAQScreen() {
/>
<div className="mx-auto max-w-5xl">
<div className="mb-4 flex items-center gap-2 rounded-md border border-[var(--color-border)] bg-[var(--color-surface-2)] px-3 py-2">
<div className="mb-4 flex items-center gap-2 rounded-[var(--radius-card)] border border-[var(--color-border)] bg-[var(--color-surface-2)] px-3 py-2">
<Search size={14} className="shrink-0 text-[var(--color-muted)]" />
<input
value={query}
@@ -254,7 +254,7 @@ export default function FAQScreen() {
<button
type="button"
onClick={() => setQuery("")}
className="rounded p-0.5 text-[var(--color-muted)] hover:bg-[var(--color-surface-3)]"
className="rounded-full p-1 text-[var(--color-muted)] hover:bg-[var(--color-surface-3)]"
aria-label={tr("faq_clear_search", "Clear search")}
>
<X size={12} />
@@ -318,7 +318,7 @@ export default function FAQScreen() {
) : (
<li
key={h.topic.id}
className="rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)]"
className="surface-panel"
>
<button
type="button"
+6 -6
View File
@@ -116,39 +116,39 @@ export default function FwSpoofScreen() {
{tr("fw_spoof_details", undefined, "Details")}
</h3>
<div className="grid grid-cols-1 gap-3 text-sm md:grid-cols-2">
<div className="flex items-center justify-between gap-2 rounded-md bg-[var(--color-surface-3)] px-3 py-2">
<div className="flex items-center justify-between gap-2 rounded-[var(--radius-field)] bg-[var(--color-surface-3)] px-3 py-2">
<span className="text-[var(--color-muted)]">
{tr("fw_spoof_sw_version", undefined, "System SW Version")}
</span>
<code className="font-mono font-semibold">{swVersionDisplay}</code>
</div>
<div className="flex items-center justify-between gap-2 rounded-md bg-[var(--color-surface-3)] px-3 py-2">
<div className="flex items-center justify-between gap-2 rounded-[var(--radius-field)] bg-[var(--color-surface-3)] px-3 py-2">
<span className="text-[var(--color-muted)]">
{tr("fw_spoof_sw_raw", undefined, "Raw SW Value")}
</span>
<code className="font-mono">{status.system_sw_raw || "—"}</code>
</div>
<div className="flex items-center justify-between gap-2 rounded-md bg-[var(--color-surface-3)] px-3 py-2">
<div className="flex items-center justify-between gap-2 rounded-[var(--radius-field)] bg-[var(--color-surface-3)] px-3 py-2">
<span className="text-[var(--color-muted)]">
{tr("fw_spoof_kernel", undefined, "Kernel Release")}
</span>
<code className="font-mono">{status.kernel_release || "—"}</code>
</div>
<div className="flex items-center justify-between gap-2 rounded-md bg-[var(--color-surface-3)] px-3 py-2">
<div className="flex items-center justify-between gap-2 rounded-[var(--radius-field)] bg-[var(--color-surface-3)] px-3 py-2">
<span className="text-[var(--color-muted)]">
{tr("fw_spoof_kernel_fw", undefined, "Kernel FW Version")}
</span>
<code className="font-mono">{status.kernel_fw_version || "—"}</code>
</div>
{status.kernel_version && (
<div className="flex items-center justify-between gap-2 rounded-md bg-[var(--color-surface-3)] px-3 py-2 md:col-span-2">
<div className="flex items-center justify-between gap-2 rounded-[var(--radius-field)] bg-[var(--color-surface-3)] px-3 py-2 md:col-span-2">
<span className="text-[var(--color-muted)]">
{tr("fw_spoof_kern_version", undefined, "Kernel Build")}
</span>
<code className="font-mono text-xs">{status.kernel_version}</code>
</div>
)}
<div className="flex items-center justify-between gap-2 rounded-md bg-[var(--color-surface-3)] px-3 py-2">
<div className="flex items-center justify-between gap-2 rounded-[var(--radius-field)] bg-[var(--color-surface-3)] px-3 py-2">
<span className="text-[var(--color-muted)]">
{tr("fw_spoof_flag", undefined, "Spoofed")}
</span>
+3 -3
View File
@@ -46,9 +46,9 @@ export default function NetworkPanel({ mgmtAddr }: { mgmtAddr: string }) {
}, [mgmtAddr]);
return (
<section className="rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)] p-4">
<header className="mb-3 flex items-center gap-2">
<Network size={14} />
<section className="surface-panel min-w-0 p-5 sm:p-6">
<header className="mb-4 flex items-center gap-2.5">
<span className="icon-disc" aria-hidden><Network size={14} /></span>
<h3 className="flex-1 text-sm font-semibold">
{tr("network_panel_title", undefined, "Network interfaces")}
</h3>
@@ -108,11 +108,11 @@ export default function PeripheralPanel({
}
return (
<section className="rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)] p-4">
<section className="surface-panel min-w-0 p-5 sm:p-6">
{drive !== "no" && (
<div data-testid="disc-controls" className="mb-4">
<header className="mb-3 flex items-center gap-2">
<Disc3 size={14} />
<header className="mb-4 flex items-center gap-2.5">
<span className="icon-disc" aria-hidden><Disc3 size={14} /></span>
<h3 className="text-sm font-semibold">
{tr("peripheral_title", undefined, "Disc drive")}
</h3>
@@ -51,9 +51,9 @@ export default function PowerTelemetryPanel({ mgmtAddr }: { mgmtAddr: string })
}, [mgmtAddr]);
return (
<section className="rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)] p-4">
<header className="mb-3 flex items-center gap-2">
<Battery size={14} />
<section className="surface-panel min-w-0 p-5 sm:p-6">
<header className="mb-4 flex items-center gap-2.5">
<span className="icon-disc" aria-hidden><Battery size={14} /></span>
<h3 className="flex-1 text-sm font-semibold">
{tr("power_telemetry_title_v2", undefined, "Lifetime counters")}
</h3>
@@ -100,7 +100,7 @@ export default function PowerTelemetryPanel({ mgmtAddr }: { mgmtAddr: string })
which of the four ICC symbols resolved, so use it rather than
guessing from the nulls. */}
{data && unavailableNote(data) && (
<p className="mt-3 rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] p-2 text-xs text-[var(--color-muted)]">
<p className="mt-3 rounded-[var(--radius-card)] border border-[var(--color-border)] bg-[var(--color-surface)] p-3 text-xs text-[var(--color-muted)]">
{tr(
`power_telemetry_note_${data.status}`,
undefined,
+31 -29
View File
@@ -437,7 +437,7 @@ export default function HardwareScreen() {
)}
<ConnectionGate require="payload">
<div className="grid gap-4 lg:grid-cols-2 xl:grid-cols-3">
<div className="grid gap-5 lg:grid-cols-2 xl:grid-cols-3">
<SensorCard
icon={<Thermometer size={14} />}
title={tr("hardware_temperatures", undefined, "Temperatures")}
@@ -770,14 +770,14 @@ function SensorCard({
children: React.ReactNode;
}) {
return (
<section className="rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)] p-4">
<header className="mb-2 flex items-center gap-2 text-xs uppercase tracking-wide text-[var(--color-muted)]">
{icon}
<span className="font-semibold">{title}</span>
<section className="surface-panel min-w-0 p-5 sm:p-6">
<header className="mb-3 flex items-center gap-2.5 text-sm font-semibold">
<span className="icon-disc" aria-hidden>
{icon}
</span>
<span>{title}</span>
</header>
<dl className="grid grid-cols-[auto_1fr] gap-x-4 gap-y-1 text-sm">
{children}
</dl>
<dl className="flex flex-col">{children}</dl>
</section>
);
}
@@ -791,18 +791,20 @@ function StatRow({
value: string;
hint?: string;
}) {
// One metric row: label left, value right, the hint (if any) under the
// value in small muted type rather than squeezed in beside it.
return (
<>
<dt className="text-[var(--color-muted)]">{label}</dt>
<dd className="font-mono tabular-nums" title={hint}>
{value}
<div className="metric-row min-w-0 py-2">
<dt className="shrink-0 text-[var(--color-muted)]">{label}</dt>
<dd className="min-w-0 text-right" title={hint}>
<div className="font-mono tabular-nums">{value}</div>
{hint && (
<span className="ml-2 text-xs text-[var(--color-muted)] font-sans">
· {hint}
</span>
<div className="mt-0.5 text-xs leading-snug text-[var(--color-muted)]">
{hint}
</div>
)}
</dd>
</>
</div>
);
}
@@ -903,10 +905,10 @@ function FanThresholdCard({
);
return (
<section className="rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)] p-4">
<section className="surface-panel min-w-0 p-5 sm:p-6">
{confirmDialog}
<header className="mb-3 flex items-center gap-2 text-xs uppercase tracking-wide text-[var(--color-muted)]">
<Fan size={14} />
<header className="mb-4 flex items-center gap-2.5 text-sm font-semibold text-[var(--color-text)]">
<span className="icon-disc" aria-hidden><Fan size={14} /></span>
<span className="font-semibold">
{tr("hardware_fan_threshold", "Fan threshold")}
</span>
@@ -962,7 +964,7 @@ function FanThresholdCard({
type="button"
onClick={() => applyThreshold(draftC)}
disabled={!canSet || lastSetC === draftC}
className="rounded-md border border-[var(--color-border)] px-3 py-1 text-xs hover:bg-[var(--color-surface-3)] disabled:opacity-50"
className="chip min-h-8 px-3.5 text-xs disabled:opacity-50"
>
{tr("hardware_apply", "Apply")}
</button>
@@ -994,7 +996,7 @@ function FanThresholdCard({
type="button"
onClick={restoreConsole}
disabled={!canSet || (pinnedC === 0 && lastSetC === null)}
className="rounded-md border border-[var(--color-border)] px-3 py-1 text-xs hover:bg-[var(--color-surface-3)] disabled:opacity-50"
className="chip min-h-8 px-3.5 text-xs disabled:opacity-50"
>
{tr("hardware_fan_restore", undefined, "Use the console's own setting")}
</button>
@@ -1074,7 +1076,7 @@ function FanCurvePreview({ thresholdC }: { thresholdC: number }) {
.map(([t, p]) => `${xFor(t).toFixed(1)},${yFor(p).toFixed(1)}`)
.join(" ");
return (
<div className="mb-3 rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] p-2">
<div className="mb-3 rounded-[var(--radius-card)] border border-[var(--color-border)] bg-[var(--color-surface)] p-3">
<div className="mb-1 flex items-center justify-between text-xs text-[var(--color-muted)]">
<span>
{tr("hardware_fan_curve_preview", "Fan curve preview (approximate)")}
@@ -1266,9 +1268,9 @@ function SystemTimeCard({
const outcome = lastResult ? classifySyncResult(lastResult) : null;
return (
<section className="rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)] p-4">
<header className="mb-3 flex items-center gap-2 text-xs uppercase tracking-wide text-[var(--color-muted)]">
<CalendarClock size={14} />
<section className="surface-panel min-w-0 p-5 sm:p-6">
<header className="mb-4 flex items-center gap-2.5 text-sm font-semibold text-[var(--color-text)]">
<span className="icon-disc" aria-hidden><CalendarClock size={14} /></span>
<span>{tr("hardware_systime_title", "System time")}</span>
</header>
@@ -1347,7 +1349,7 @@ function SystemTimeCard({
{/* Result — success / success-via-fallback / no-op / failure */}
{lastResult && !busy && (
<div className="mt-3 rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] p-2 text-[11px]">
<div className="mt-3 rounded-[var(--radius-card)] border border-[var(--color-border)] bg-[var(--color-surface)] p-3 text-[11px]">
{outcome === "stub_no_op" ? (
<div className="text-[var(--color-bad)]">
{tr(
@@ -1541,9 +1543,9 @@ function SmpMetaCard({
: tr("smp_meta_never_run", "never");
return (
<section className="rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)] p-4">
<header className="mb-3 flex items-center gap-2 text-xs uppercase tracking-wide text-[var(--color-muted)]">
<ImageIcon size={14} />
<section className="surface-panel min-w-0 p-5 sm:p-6">
<header className="mb-4 flex items-center gap-2.5 text-sm font-semibold text-[var(--color-text)]">
<span className="icon-disc" aria-hidden><ImageIcon size={14} /></span>
<span className="font-semibold">
{tr("smp_meta_title", "SMP appmeta heal")}
</span>
+3 -3
View File
@@ -101,7 +101,7 @@ export function SpeedTestCard({ host }: { host: string }) {
value={sizeMib}
disabled={running}
onChange={(e) => setSizeMib(Number(e.currentTarget.value))}
className="rounded border border-[var(--color-border)] bg-[var(--color-surface-1)] px-2 py-1 text-sm text-[var(--color-text)]"
className="rounded border border-[var(--color-border)] bg-[var(--color-surface)] px-2 py-1 text-sm text-[var(--color-text)]"
>
{SIZES.map((s) => (
<option key={s} value={s}>
@@ -155,7 +155,7 @@ export function SpeedTestCard({ host }: { host: string }) {
className="mt-3 grid gap-3 sm:grid-cols-2"
data-testid="speed-test-result"
>
<div className="rounded-md border border-[var(--color-border)] bg-[var(--color-surface-1)] p-3">
<div className="rounded-[var(--radius-card)] border border-[var(--color-border)] bg-[var(--color-surface)] p-3">
<dt className="text-xs text-[var(--color-muted)]">
{tr("speedtest.up", undefined, "To the PS5 (upload)")}
</dt>
@@ -163,7 +163,7 @@ export function SpeedTestCard({ host }: { host: string }) {
{st.uploadBps !== null ? perSec(st.uploadBps) : "—"}
</dd>
</div>
<div className="rounded-md border border-[var(--color-border)] bg-[var(--color-surface-1)] p-3">
<div className="rounded-[var(--radius-card)] border border-[var(--color-border)] bg-[var(--color-surface)] p-3">
<dt className="text-xs text-[var(--color-muted)]">
{tr("speedtest.down", undefined, "From the PS5 (download)")}
</dt>
+4 -4
View File
@@ -236,7 +236,7 @@ export default function NanoDnsScreen() {
/>
) : null}
<div className="flex flex-col gap-2 rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)] p-4">
<div className="surface-panel flex flex-col gap-2 p-5">
<div className="flex flex-wrap items-center gap-2">
<Badge
tone={
@@ -305,7 +305,7 @@ export default function NanoDnsScreen() {
) : null}
{version.generation === "modern" && text !== null ? (
<div className="grid gap-4 rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)] p-4 md:grid-cols-2">
<div className="grid gap-4 rounded-[var(--radius-card)] border border-[var(--color-border)] bg-[var(--color-surface-2)] p-4 md:grid-cols-2">
<Toggle
checked={quietEnabled}
disabled={saving}
@@ -340,7 +340,7 @@ export default function NanoDnsScreen() {
</div>
) : null}
<div className="flex flex-col gap-2 rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)] p-4">
<div className="surface-panel flex flex-col gap-2 p-5">
<div className="flex items-center justify-between">
<code className="text-xs text-[var(--color-muted)]">
{NANODNS_INI_PATH}
@@ -371,7 +371,7 @@ export default function NanoDnsScreen() {
value={text ?? ""}
onChange={(e) => setText(e.target.value)}
spellCheck={false}
className="h-[28rem] w-full resize-y rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] p-3 font-mono text-xs leading-relaxed outline-none focus:border-[var(--color-accent)]"
className="h-[28rem] w-full resize-y rounded-[var(--radius-field)] border border-[var(--glass-edge)] bg-[var(--color-surface-raised)] p-3 font-mono text-xs leading-relaxed outline-none focus:border-[var(--color-accent)]"
/>
<p className="text-xs text-[var(--color-muted)]">
{tr(
+11 -9
View File
@@ -159,12 +159,14 @@ export default function NotificationsScreen() {
/>
<ConnectionGate>
{error && <ErrorCard title={error} />}
{error && <ErrorCard title={error} onRetry={() => void refresh()} />}
{unreadCount > 0 && (
<div className="flex flex-wrap items-center justify-between gap-2 rounded-lg border border-[var(--color-accent)]/40 bg-[var(--color-accent-soft)] px-4 py-2 text-sm">
<span className="flex items-center gap-2 text-[var(--color-accent)]">
<Mail size={14} />
<div className="flex flex-wrap items-center justify-between gap-2 rounded-[var(--radius-card)] border border-[var(--glass-edge)] bg-[var(--color-surface-raised)] px-4 py-2.5 text-sm shadow-[var(--edge-highlight)]">
<span className="flex items-center gap-2.5 font-medium">
<span className="icon-disc !h-7 !w-7" aria-hidden>
<Mail size={13} />
</span>
{tr(
"notifications_unread",
{ count: unreadCount },
@@ -203,17 +205,17 @@ export default function NotificationsScreen() {
return (
<div
key={n.seq}
className={`rounded-md border px-3 py-2.5 transition-colors ${
className={`rounded-[var(--radius-card)] border px-4 py-3 transition-colors ${
read
? "border-[var(--color-border)] bg-[var(--color-surface-2)]"
: "border-[var(--color-accent)]/40 bg-[var(--color-accent-soft)]"
? "border-[var(--color-border)] bg-[var(--color-surface)]"
: "border-[var(--glass-edge)] bg-[var(--color-surface-raised)] shadow-[var(--edge-highlight),var(--shadow-1)]"
}`}
>
<div className="flex items-start justify-between gap-3">
<span
aria-hidden
className={`mt-1.5 h-2 w-2 shrink-0 rounded-full ${
read ? "bg-transparent" : "bg-[var(--color-accent)]"
read ? "bg-transparent" : "bg-[var(--color-accent-bright)]"
}`}
/>
<div className="min-w-0 flex-1">
@@ -237,7 +239,7 @@ export default function NotificationsScreen() {
<button
type="button"
onClick={() => host && setRead(host, n.seq, !read)}
className="flex shrink-0 items-center gap-1 rounded px-2 py-1 text-xs text-[var(--color-muted)] hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text)]"
className="flex shrink-0 items-center gap-1 rounded-full px-2.5 py-1 text-xs text-[var(--color-muted)] hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text)] max-md:min-h-11"
>
{read ? <Mail size={12} /> : <MailOpen size={12} />}
{read
+12 -12
View File
@@ -360,7 +360,7 @@ export default function CatalogPanel() {
with ps5upload is the guidance that was missing — kept as plain
text rather than buttons so it never implies these are installed
or endorsed beyond "start here". */}
<div className="rounded-md border border-[var(--color-accent)] bg-[var(--color-surface)] p-3">
<div className="rounded-[var(--radius-card)] border border-[var(--color-accent)] bg-[var(--color-surface)] p-3">
<p className="text-xs font-semibold">
{tr("payloads_essentials_title", undefined, "Recommended alongside ps5upload")}
</p>
@@ -372,7 +372,7 @@ export default function CatalogPanel() {
)}
</p>
</div>
<div className="flex flex-wrap items-center justify-between gap-3 rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] p-3">
<div className="flex flex-wrap items-center justify-between gap-3 rounded-[var(--radius-card)] border border-[var(--color-border)] bg-[var(--color-surface)] p-3">
<div className="text-xs text-[var(--color-muted)]">
{tr(
"payloads_curated_blurb",
@@ -420,7 +420,7 @@ export default function CatalogPanel() {
</div>
</div>
{addOpen && (
<div className="rounded-md border border-[var(--color-border)] bg-[var(--color-surface-2)] p-3">
<div className="rounded-[var(--radius-card)] border border-[var(--color-border)] bg-[var(--color-surface-2)] p-3">
<div className="mb-2 flex items-center justify-between">
<span className="text-xs font-medium text-[var(--color-text)]">
{tr("payloads_add_repo_title", undefined, "Track a custom payload repo")}
@@ -470,7 +470,7 @@ export default function CatalogPanel() {
undefined,
"Used only when you enter a bare owner/name. GitHub or a Gitea/Forgejo host.",
)}
className="w-36 rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] px-2 py-1.5 text-xs outline-none focus:border-[var(--color-accent)]"
className="w-36 rounded-full border border-[var(--glass-edge)] bg-[var(--color-surface-raised)] px-2 py-1.5 text-xs outline-none focus:border-[var(--color-accent)]"
/>
</label>
<Button
@@ -484,7 +484,7 @@ export default function CatalogPanel() {
</Button>
</div>
{addError && (
<div className="mt-2 text-xs text-[var(--color-danger)]">{addError}</div>
<div className="mt-2 text-xs text-[var(--color-bad)]">{addError}</div>
)}
</div>
)}
@@ -514,7 +514,7 @@ export default function CatalogPanel() {
/>
)}
{query.trim() && visibleCatalog.length === 0 && (
<div className="rounded-md border border-dashed border-[var(--color-border)] p-6 text-center text-xs text-[var(--color-muted)]">
<div className="rounded-[var(--radius-card)] border border-dashed border-[var(--color-border)] p-6 text-center text-xs text-[var(--color-muted)]">
{tr(
"payloads_search_no_match",
{ query: query.trim() },
@@ -600,7 +600,7 @@ function PayloadCard({
const updateAvailable =
!!local && !!release && !!release.tag && local.version !== release.tag;
return (
<section className="rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)] p-4">
<section className="surface-panel p-5">
<div className="flex flex-wrap items-start gap-3">
{/* Floor the text column's min-width to min(100%,16rem). With `flex-1`
(basis 0%) + `min-w-0`, the name column shrank to nothing at large
@@ -630,7 +630,7 @@ function PayloadCard({
<button
type="button"
onClick={onRemove}
className="inline-flex items-center gap-1 text-xs text-[var(--color-muted)] hover:text-[var(--color-danger)]"
className="inline-flex items-center gap-1 text-xs text-[var(--color-muted)] hover:text-[var(--color-bad)]"
title={tr("payloads_custom_remove", undefined, "Remove this custom repo")}
>
<Trash2 size={11} />
@@ -705,7 +705,7 @@ function PayloadCard({
value={selectedTag}
onChange={(e) => onSelectTag(e.target.value)}
disabled={busy}
className="max-w-[12rem] rounded border border-[var(--color-border)] bg-[var(--color-surface)] px-1.5 py-1 text-xs"
className="max-w-[12rem] rounded-full border border-[var(--glass-edge)] bg-[var(--color-surface-raised)] px-1.5 py-1 text-xs"
title={tr(
"payloads_version_tooltip",
undefined,
@@ -768,7 +768,7 @@ function PayloadCard({
</div>
{release && (
<div className="mt-3 rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] p-2 text-xs">
<div className="mt-3 rounded-[var(--radius-card)] border border-[var(--color-border)] bg-[var(--color-surface)] p-3 text-xs">
<div className="flex flex-wrap items-center gap-x-3 gap-y-1 text-[var(--color-muted)]">
<span>
{tr(
@@ -809,7 +809,7 @@ function PayloadCard({
// user has no way to tell live data from N-day-stale
// cache, and might wonder why the "latest" version
// doesn't match a release they just saw on GitHub.
<div className="mt-2 flex items-start gap-1.5 rounded-md border border-[var(--color-warn)] bg-[var(--color-surface)] p-2 text-xs text-[var(--color-warn)]">
<div className="mt-2 flex items-start gap-1.5 rounded-[var(--radius-card)] border border-[var(--color-warn)] bg-[var(--color-surface)] p-3 text-xs text-[var(--color-warn)]">
<AlertTriangle size={11} className="mt-0.5 shrink-0" />
<span>
{tr(
@@ -836,7 +836,7 @@ function PayloadCard({
// (a renamed/deleted repo → 404, or a down Forgejo host) is not a
// user error — the row stays present and usable later. The loud red
// card made users think the payload was "broken" or "removed".
<div className="mt-3 flex items-start gap-2 rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] p-2 text-xs text-[var(--color-muted)]">
<div className="mt-3 flex items-start gap-2 rounded-[var(--radius-card)] border border-[var(--color-border)] bg-[var(--color-surface)] p-3 text-xs text-[var(--color-muted)]">
<AlertTriangle size={11} className="mt-0.5 shrink-0" />
<span className="min-w-0 break-words">
{tr(
+15 -15
View File
@@ -192,7 +192,7 @@ export function PlaylistsPanel({ host, port }: { host: string; port: number }) {
.slice(0, 5);
return (
<section className="mt-8 rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)] p-5">
<section className="mt-8 rounded-[var(--radius-card)] border border-[var(--color-border)] bg-[var(--color-surface-2)] p-5">
<header className="mb-4 flex flex-wrap items-center justify-between gap-3">
<div>
<h2 className="flex items-center gap-2 text-sm font-semibold">
@@ -341,7 +341,7 @@ export function PlaylistsPanel({ host, port }: { host: string; port: number }) {
)}
{playlists.length === 0 && (
<div className="rounded-md border border-dashed border-[var(--color-border)] bg-[var(--color-surface)] p-4 text-center text-xs text-[var(--color-muted)]">
<div className="rounded-[var(--radius-card)] border border-dashed border-[var(--color-border)] bg-[var(--color-surface)] p-4 text-center text-xs text-[var(--color-muted)]">
{tr(
"playlists_empty",
undefined,
@@ -389,7 +389,7 @@ function AutoLoaderCard() {
const warn = autoLoader.enabled && (!selected || selected.steps.length === 0);
return (
<div className="mb-4 rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] p-3">
<div className="mb-4 rounded-[var(--radius-card)] border border-[var(--color-border)] bg-[var(--color-surface)] p-3">
<div className="flex flex-col gap-3 sm:flex-row sm:flex-wrap sm:items-center sm:justify-between">
<div className="flex min-w-0 items-start gap-2">
<Zap
@@ -511,7 +511,7 @@ function RunStatusBanner({ host }: { host: string }) {
if (runStatus.kind === "running") {
const step = playlist?.steps[runStatus.stepIndex];
return (
<div className="mb-3 flex items-center gap-2 rounded-md border border-[var(--color-accent)] bg-[var(--color-surface)] p-2 text-xs">
<div className="mb-3 flex items-center gap-2 rounded-[var(--radius-card)] border border-[var(--color-accent)] bg-[var(--color-surface)] p-3 text-xs">
<Spinner size={14} tone="accent" />
<span className="font-medium">{name}</span>
<ConsoleChip addr={runStatus.host} />
@@ -534,7 +534,7 @@ function RunStatusBanner({ host }: { host: string }) {
const elapsed = now - runStatus.sleepStartedAtMs;
const remaining = Math.max(0, runStatus.sleepDurationMs - elapsed);
return (
<div className="mb-3 flex items-center gap-2 rounded-md border border-[var(--color-accent)] bg-[var(--color-surface)] p-2 text-xs">
<div className="mb-3 flex items-center gap-2 rounded-[var(--radius-card)] border border-[var(--color-accent)] bg-[var(--color-surface)] p-3 text-xs">
<Clock size={14} className="text-[var(--color-accent)]" />
<span className="font-medium">{name}</span>
<ConsoleChip addr={runStatus.host} />
@@ -715,7 +715,7 @@ function PlaylistCard({
const canRun = playlist.steps.length > 0 && !!host?.trim() && !anyRunning;
return (
<article className="rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] p-4">
<article className="rounded-[var(--radius-card)] border border-[var(--color-border)] bg-[var(--color-surface)] p-4">
<header className="mb-3 flex flex-wrap items-center justify-between gap-2">
<div className="flex min-w-0 items-center gap-2">
{/* Reorder this playlist within the list. Mirrors the per-step
@@ -726,7 +726,7 @@ function PlaylistCard({
onClick={() => movePlaylistUp(playlist.id)}
disabled={anyRunning || index === 0}
aria-label={tr("playlist_move_up", undefined, "Move playlist up")}
className="rounded p-1 text-[var(--color-muted)] hover:bg-[var(--color-surface-3)] disabled:opacity-30"
className="rounded-full p-1.5 text-[var(--color-muted)] hover:bg-[var(--color-surface-3)] disabled:opacity-30"
title={tr("playlist_move_up", undefined, "Move playlist up")}
>
<ArrowUp size={12} />
@@ -736,7 +736,7 @@ function PlaylistCard({
onClick={() => movePlaylistDown(playlist.id)}
disabled={anyRunning || index === total - 1}
aria-label={tr("playlist_move_down", undefined, "Move playlist down")}
className="rounded p-1 text-[var(--color-muted)] hover:bg-[var(--color-surface-3)] disabled:opacity-30"
className="rounded-full p-1.5 text-[var(--color-muted)] hover:bg-[var(--color-surface-3)] disabled:opacity-30"
title={tr("playlist_move_down", undefined, "Move playlist down")}
>
<ArrowDown size={12} />
@@ -757,7 +757,7 @@ function PlaylistCard({
setRenaming(false);
}
}}
className="rounded-md border border-[var(--color-accent)] bg-[var(--color-surface)] px-2 py-0.5 text-sm font-semibold outline-none"
className="rounded-full border border-[var(--color-accent)] bg-[var(--color-surface)] px-2 py-0.5 text-sm font-semibold outline-none"
/>
) : (
<button
@@ -974,7 +974,7 @@ function PlaylistCard({
})
}
disabled={anyRunning}
className="w-28 rounded border border-[var(--color-border)] bg-[var(--color-surface)] px-1 py-0.5 text-xs"
className="w-28 rounded-full border border-[var(--glass-edge)] bg-[var(--color-surface-raised)] px-1 py-0.5 text-xs"
/>
</label>
{/* Per-step port override. Empty / 0 = use the
@@ -1019,7 +1019,7 @@ function PlaylistCard({
})
}
disabled={anyRunning}
className="w-16 rounded border border-[var(--color-border)] bg-[var(--color-surface)] px-1 py-0.5 text-right text-xs tabular-nums"
className="w-16 rounded-full border border-[var(--glass-edge)] bg-[var(--color-surface-raised)] px-1 py-0.5 text-right text-xs tabular-nums"
/>
<span>ms</span>
</label>
@@ -1029,7 +1029,7 @@ function PlaylistCard({
onClick={() => moveStepUp(playlist.id, i)}
disabled={anyRunning || i === 0}
aria-label={tr("playlist_step_up", undefined, "Move up")}
className="rounded p-1 text-[var(--color-muted)] hover:bg-[var(--color-surface-3)] disabled:opacity-30"
className="rounded-full p-1.5 text-[var(--color-muted)] hover:bg-[var(--color-surface-3)] disabled:opacity-30"
title={tr("playlist_step_up", undefined, "Move up")}
>
<ArrowUp size={12} />
@@ -1039,7 +1039,7 @@ function PlaylistCard({
onClick={() => moveStepDown(playlist.id, i)}
disabled={anyRunning || i === playlist.steps.length - 1}
aria-label={tr("playlist_step_down", undefined, "Move down")}
className="rounded p-1 text-[var(--color-muted)] hover:bg-[var(--color-surface-3)] disabled:opacity-30"
className="rounded-full p-1.5 text-[var(--color-muted)] hover:bg-[var(--color-surface-3)] disabled:opacity-30"
title={tr("playlist_step_down", undefined, "Move down")}
>
<ArrowDown size={12} />
@@ -1049,7 +1049,7 @@ function PlaylistCard({
onClick={() => removeStep(playlist.id, i)}
disabled={anyRunning}
aria-label={tr("playlist_step_remove", undefined, "Remove step")}
className="rounded p-1 text-[var(--color-muted)] hover:bg-[var(--color-bad)] hover:text-[var(--color-accent-contrast)] disabled:opacity-30"
className="rounded-full p-1.5 text-[var(--color-muted)] hover:bg-[var(--color-bad)] hover:text-[var(--color-accent-contrast)] disabled:opacity-30"
title={tr("playlist_step_remove", undefined, "Remove step")}
>
<X size={12} />
@@ -1134,7 +1134,7 @@ function PlaylistCard({
<button
type="button"
onClick={() => handleAddRepoStep(p)}
className="flex w-full items-center gap-2 rounded-md border border-[var(--color-border)] bg-[var(--color-surface-2)] p-2 text-left text-xs hover:border-[var(--color-accent)]"
className="flex w-full items-center gap-2 rounded-[var(--radius-card)] border border-[var(--color-border)] bg-[var(--color-surface-2)] p-3 text-left text-xs hover:border-[var(--color-accent)]"
>
<PackageIcon
size={14}
+7 -7
View File
@@ -388,7 +388,7 @@ export default function SendPanel() {
instead. `min(100%,24rem)` keeps a lone column from overflowing a
narrow viewport. */}
<div className="grid gap-6 grid-cols-[repeat(auto-fit,minmax(min(100%,24rem),1fr))]">
<section className="rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)] p-5">
<section className="rounded-[var(--radius-card)] border border-[var(--color-border)] bg-[var(--color-surface-2)] p-5">
{/* IP + port. Stacks vertically by default and only goes
side-by-side at sm+, with a FLOORED IP column (minmax 10rem) and
an elastic port column. The old fixed `1fr_7rem` collapsed the IP
@@ -575,7 +575,7 @@ export default function SendPanel() {
</div>
{status.kind === "sent" && (
<div className="mt-4 flex items-start gap-2 rounded-md border border-[var(--color-good)] bg-[var(--color-surface-3)] p-3 text-xs">
<div className="mt-4 flex items-start gap-2 rounded-[var(--radius-card)] border border-[var(--color-good)] bg-[var(--color-surface-3)] p-3 text-xs">
<CheckCircle2
size={14}
className="mt-0.5 text-[var(--color-good)]"
@@ -601,7 +601,7 @@ export default function SendPanel() {
)}
{status.kind === "failed" && (
<div className="mt-4 flex items-start gap-2 rounded-md border border-[var(--color-bad)] bg-[var(--color-surface-3)] p-3 text-xs">
<div className="mt-4 flex items-start gap-2 rounded-[var(--radius-card)] border border-[var(--color-bad)] bg-[var(--color-surface-3)] p-3 text-xs">
<XCircle size={14} className="mt-0.5 text-[var(--color-bad)]" />
<div>
<div className="font-medium text-[var(--color-bad)]">
@@ -649,7 +649,7 @@ function HistoryPanel({
}) {
const tr = useTr();
return (
<aside className="rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)] p-4">
<aside className="surface-panel p-5">
<header className="mb-3 flex items-center justify-between">
<div className="flex items-center gap-2">
<History size={14} />
@@ -662,7 +662,7 @@ function HistoryPanel({
onClick={onClear}
disabled={records.length === 0}
title={tr("sendpayload_clear_history", undefined, "Clear history")}
className="flex items-center gap-1 rounded-md border border-[var(--color-border)] px-2 py-1 text-xs hover:bg-[var(--color-surface-3)] disabled:opacity-40"
className="flex items-center gap-1 rounded-full border border-[var(--color-border)] px-2 py-1 text-xs hover:bg-[var(--color-surface-3)] disabled:opacity-40"
>
<Trash2 size={11} />
{tr("sendpayload_clear", undefined, "Clear")}
@@ -670,7 +670,7 @@ function HistoryPanel({
</header>
{records.length === 0 ? (
<div className="rounded-md border border-dashed border-[var(--color-border)] p-4 text-center text-xs text-[var(--color-muted)]">
<div className="rounded-[var(--radius-card)] border border-dashed border-[var(--color-border)] p-4 text-center text-xs text-[var(--color-muted)]">
{tr(
"sendpayload_no_sends_yet",
undefined,
@@ -685,7 +685,7 @@ function HistoryPanel({
type="button"
onClick={() => onReplay(rec)}
title={rec.path}
className="group flex w-full items-start gap-2 rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] p-2 text-left text-xs hover:border-[var(--color-accent)] hover:bg-[var(--color-surface-3)]"
className="group flex w-full items-start gap-2 rounded-[var(--radius-card)] border border-[var(--color-border)] bg-[var(--color-surface)] p-3 text-left text-xs hover:border-[var(--color-accent)] hover:bg-[var(--color-surface-3)]"
>
<StatusDot ok={rec.status === "success"} />
<div className="min-w-0 flex-1">
@@ -114,7 +114,7 @@ export default function UsbAutoloaderModal({
>
<div className="space-y-5 p-5 text-sm">
{mobile ? (
<div className="flex flex-col items-center gap-2 rounded-md border border-dashed border-[var(--color-border)] p-6 text-center">
<div className="flex flex-col items-center gap-2 rounded-[var(--radius-card)] border border-dashed border-[var(--color-border)] p-6 text-center">
<HardDrive size={24} className="text-[var(--color-muted)]" />
<div className="text-sm font-medium">
{tr(
@@ -161,7 +161,7 @@ export default function UsbAutoloaderModal({
{tr("usb_wizard_scanning", undefined, "Scanning…")}
</div>
) : drives.length === 0 ? (
<div className="rounded-md border border-dashed border-[var(--color-border)] p-3 text-xs text-[var(--color-muted)]">
<div className="rounded-[var(--radius-card)] border border-dashed border-[var(--color-border)] p-3 text-xs text-[var(--color-muted)]">
{tr(
"usb_wizard_no_drives",
undefined,
@@ -321,7 +321,7 @@ export default function UsbAutoloaderModal({
)}
{result && (
<div className="mt-2 space-y-2 text-xs">
<div className="rounded-md border border-[var(--color-good)] bg-[var(--color-surface)] p-2">
<div className="rounded-[var(--radius-card)] border border-[var(--color-good)] bg-[var(--color-surface)] p-3">
<div className="font-medium text-[var(--color-good)]">
{tr(
"usb_wizard_done",
@@ -336,7 +336,7 @@ export default function UsbAutoloaderModal({
</ul>
</div>
{result.skipped.length > 0 && (
<div className="rounded-md border border-[var(--color-warn)] bg-[var(--color-surface)] p-2">
<div className="rounded-[var(--radius-card)] border border-[var(--color-warn)] bg-[var(--color-surface)] p-3">
<div className="font-medium text-[var(--color-warn)]">
{tr("usb_wizard_skipped", undefined, "Skipped")}
</div>
@@ -347,7 +347,7 @@ export default function UsbAutoloaderModal({
</ul>
</div>
)}
<details className="rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] p-2">
<details className="rounded-[var(--radius-card)] border border-[var(--color-border)] bg-[var(--color-surface)] p-3">
<summary className="cursor-pointer text-[var(--color-muted)]">
{tr(
"usb_wizard_show_autoload_txt",
+11 -14
View File
@@ -279,7 +279,7 @@ function AvatarSection({
</div>
{noTarget && (
<div className="mb-3 rounded-md border border-[var(--color-warn)] bg-[var(--color-surface)] p-3 text-xs text-[var(--color-muted)]">
<div className="mb-3 rounded-[var(--radius-card)] border border-[var(--color-warn)] bg-[var(--color-surface)] p-3 text-xs text-[var(--color-muted)]">
{tr(
"profile.avatar.noUser",
"No console user found. Sign in to a profile on the PS5, then refresh.",
@@ -423,11 +423,8 @@ function ModeButton({
<button
type="button"
onClick={onClick}
className={`flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-xs font-medium transition ${
active
? "border-[var(--color-accent)] bg-[var(--color-accent)]/10 text-[var(--color-accent)]"
: "border-[var(--color-border)] text-[var(--color-muted)] hover:bg-[var(--color-surface-3)]"
}`}
aria-pressed={active}
className="chip min-h-8 gap-1.5 px-3.5 text-xs font-medium max-md:min-h-11"
>
{icon}
{label}
@@ -625,7 +622,7 @@ function UserRow({
}
return (
<div className="rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] p-2">
<div className="rounded-[var(--radius-card)] border border-[var(--color-border)] bg-[var(--color-surface)] p-3">
{confirmDialog}
<div className="flex items-center gap-2">
<span
@@ -635,7 +632,7 @@ function UserRow({
{uidHex}
</span>
<input
className="min-w-0 flex-1 rounded-md border border-[var(--color-border)] bg-[var(--color-surface-2)] px-2 py-1 text-sm"
className="min-w-0 flex-1 rounded-full border border-[var(--glass-edge)] bg-[var(--color-surface-raised)] px-2 py-1 text-sm"
value={draft}
maxLength={16}
placeholder={tr("profile.username.placeholder", "User name")}
@@ -661,7 +658,7 @@ function UserRow({
onClick={doDelete}
disabled={deleting}
title={tr("profile.username.deleteTitle", "Delete User")}
className="shrink-0 rounded-md border border-[var(--color-border)] p-1.5 text-[var(--color-warn)] hover:bg-[var(--color-surface-3)] disabled:opacity-50"
className="shrink-0 rounded-full border border-[var(--color-border)] p-1.5 text-[var(--color-warn)] hover:bg-[var(--color-surface-3)] disabled:opacity-50"
>
{deleting ? <Spinner size={14} /> : <Trash2 size={14} />}
</button>
@@ -709,12 +706,12 @@ function CreateUserRow({
}
return (
<div className="rounded-md border border-[var(--color-accent)]/40 bg-[var(--color-surface)] p-2">
<div className="rounded-[var(--radius-card)] border border-[var(--color-accent)]/40 bg-[var(--color-surface)] p-3">
<div className="flex items-center gap-2">
<UserPlus size={14} className="shrink-0 text-[var(--color-accent)]" />
<input
autoFocus
className="min-w-0 flex-1 rounded-md border border-[var(--color-border)] bg-[var(--color-surface-2)] px-2 py-1 text-sm"
className="min-w-0 flex-1 rounded-full border border-[var(--glass-edge)] bg-[var(--color-surface-raised)] px-2 py-1 text-sm"
value={name}
maxLength={16}
placeholder={tr("profile.username.newPlaceholder", "New user name")}
@@ -980,14 +977,14 @@ function SlotRow({
}
return (
<div className="rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] p-2">
<div className="rounded-[var(--radius-card)] border border-[var(--color-border)] bg-[var(--color-surface)] p-3">
{confirmDialog}
<div className="flex items-center gap-2">
<span className="w-12 shrink-0 text-xs tabular-nums text-[var(--color-muted)]">
{tr("profile.username.slot", { n: slot }, `Slot ${slot}`)}
</span>
<input
className="min-w-0 flex-1 rounded-md border border-[var(--color-border)] bg-[var(--color-surface-2)] px-2 py-1 text-sm"
className="min-w-0 flex-1 rounded-full border border-[var(--glass-edge)] bg-[var(--color-surface-raised)] px-2 py-1 text-sm"
value={draft}
maxLength={31}
onChange={(e) => {
@@ -1023,7 +1020,7 @@ function SlotRow({
{editingId ? (
<>
<input
className="w-52 rounded-md border border-[var(--color-border)] bg-[var(--color-surface-2)] px-2 py-1 font-mono text-xs"
className="w-52 rounded-full border border-[var(--glass-edge)] bg-[var(--color-surface-raised)] px-2 py-1 font-mono text-xs"
value={idDraft}
spellCheck={false}
placeholder="0x0123456789abcdef"
@@ -17,7 +17,7 @@ export function NpSignInCard({ onOpenProfile }: { onOpenProfile: () => void }) {
const tr = useTr();
return (
<div
className="rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)] p-4"
className="surface-panel p-5"
data-testid="np-signin-card"
>
<div className="mb-2 flex items-center gap-2 text-sm font-medium">
+7 -7
View File
@@ -153,7 +153,7 @@ function ReadinessPanel({
}
return (
<div className="rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)] p-4">
<div className="surface-panel p-5">
<div className="mb-3 flex items-center justify-between">
<span className="text-sm font-medium">
{tr("remotePlay_readiness", undefined, "Before you pair")}
@@ -167,7 +167,7 @@ function ReadinessPanel({
<ul className="space-y-2">
{rows.map((r) => (
<li key={r.key} className="flex items-start gap-2 text-sm">
<span className={r.ok ? "text-emerald-500" : "text-amber-500"}>
<span className={r.ok ? "text-[var(--color-good)]" : "text-[var(--color-warn)]"}>
{r.ok ? <Check size={16} /> : <X size={16} />}
</span>
<span className="flex-1">
@@ -367,7 +367,7 @@ export default function RemotePlayScreen() {
<NpSignInCard onOpenProfile={() => navigate("/profile")} />
{/* Request form */}
<div className="rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)] p-4">
<div className="surface-panel p-5">
<div className="space-y-3">
<div>
<label className="mb-1 block text-xs text-[var(--color-muted)]">
@@ -386,7 +386,7 @@ export default function RemotePlayScreen() {
undefined,
"auto-detect",
)}
className="w-full rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] px-3 py-2 text-sm text-[var(--color-text)] placeholder:text-[var(--color-muted)] focus:border-[var(--color-accent)] focus:outline-none"
className="w-full rounded-full border border-[var(--glass-edge)] bg-[var(--color-surface-raised)] px-3 py-2 text-sm text-[var(--color-text)] placeholder:text-[var(--color-muted)] focus:border-[var(--color-accent)] focus:outline-none"
/>
</div>
<div className="flex gap-2">
@@ -419,7 +419,7 @@ export default function RemotePlayScreen() {
{/* Status display */}
{status && (
<div className="rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)] p-4">
<div className="surface-panel p-5">
<h3 className="mb-3 text-sm font-medium text-[var(--color-text)]">
{tr("remotePlay_status", undefined, "Status")}
</h3>
@@ -534,8 +534,8 @@ export default function RemotePlayScreen() {
</dd>
</dl>
{status.err && (
<div className="mt-3 rounded-md border border-[var(--color-error)] bg-[var(--color-error-bg,transparent)] p-3">
<p className="text-xs text-[var(--color-error)]">
<div className="mt-3 rounded-[var(--radius-card)] border border-[var(--color-bad)] bg-[var(--color-bad-soft)] p-3">
<p className="text-xs text-[var(--color-bad)]">
{status.err}
</p>
</div>
+2 -2
View File
@@ -24,7 +24,7 @@ export default function AutoBackupCard() {
// Same centred column as the save list below it, so the two line up on a wide window.
return (
<section
className="mx-auto mb-4 max-w-4xl rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)] p-4"
className="mx-auto mb-4 max-w-4xl rounded-[var(--radius-card)] border border-[var(--color-border)] bg-[var(--color-surface-2)] p-4"
data-testid="auto-save-backup"
>
<header className="mb-1 flex items-center gap-2">
@@ -64,7 +64,7 @@ export default function AutoBackupCard() {
max={50}
value={keep}
onChange={(e) => set({ keep: clampKeep(Number(e.target.value)) })}
className="w-20 justify-self-start rounded border border-[var(--color-border)] bg-[var(--color-surface)] px-2 py-1 text-sm text-[var(--color-text)] sm:justify-self-end"
className="w-20 justify-self-start rounded-full border border-[var(--glass-edge)] bg-[var(--color-surface-raised)] px-2 py-1 text-sm text-[var(--color-text)] sm:justify-self-end"
/>
</div>
<label className="mt-3 flex items-center gap-2 border-t border-[var(--color-border)] pt-3 text-sm">
+3 -3
View File
@@ -883,7 +883,7 @@ export default function SavesScreen() {
<div className="mx-auto max-w-4xl space-y-3">
{saves?.some((e) => e.kind === "ps4") && (
<div className="rounded-lg border border-[var(--color-warn)]/40 bg-[var(--color-warn)]/10 p-3 text-xs text-[var(--color-warn)]">
<div className="rounded-[var(--radius-card)] border border-[var(--color-warn)]/40 bg-[var(--color-warn)]/10 p-3 text-xs text-[var(--color-warn)]">
<strong>
{tr("saves_ps4_warning_title", undefined, "PS4-format saves:")}
</strong>{" "}
@@ -897,7 +897,7 @@ export default function SavesScreen() {
{grouped.map(({ title_id, entries }) => (
<section
key={title_id}
className="rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)] p-4"
className="surface-panel p-5"
>
<SaveGroupHeader
host={host}
@@ -909,7 +909,7 @@ export default function SavesScreen() {
{entries.map((e) => (
<li
key={e.path}
className="flex items-center gap-3 rounded-md bg-[var(--color-surface)] px-2 py-1.5 text-xs"
className="flex items-center gap-3 rounded-[var(--radius-field)] bg-[var(--color-surface)] px-2 py-1.5 text-xs"
>
<span className="rounded bg-[var(--color-surface-3)] px-1.5 py-0.5 text-xs uppercase">
{e.kind}
+17 -21
View File
@@ -343,7 +343,7 @@ function FakelibCorpusSection() {
</span>
<button
type="button"
className="text-[var(--color-muted)] hover:text-[var(--color-danger)]"
className="text-[var(--color-muted)] hover:text-[var(--color-bad)]"
onClick={() => void remove(set.id, set.label)}
>
{tr("fakelibs_set_remove", undefined, "Remove")}
@@ -885,7 +885,7 @@ export default function SettingsScreen() {
"The main preferences (theme, language, engine, save path, upload defaults, accessibility, sidebar) are mirrored to this JSON file. You can edit it by hand; edits take effect the next time you launch the app. It does not hold everything: use Backup / restore to carry all your preferences.",
)}
</div>
<div className="mt-2 truncate rounded-md bg-[var(--color-surface)] px-2 py-1 font-mono text-xs">
<div className="mt-2 truncate rounded-[var(--radius-field)] bg-[var(--color-surface)] px-2 py-1 font-mono text-xs">
{cfgPath ?? tr("resolving", undefined, "resolving…")}
</div>
</div>
@@ -1006,7 +1006,7 @@ function SchedulesPanel() {
{schedules.map((s) => (
<li
key={s.id}
className="flex items-center gap-2 rounded-md border border-[var(--color-border)] p-2 text-xs"
className="flex items-center gap-2 rounded-[var(--radius-card)] border border-[var(--color-border)] p-3 text-xs"
>
<Toggle
className="flex-1"
@@ -1050,7 +1050,7 @@ function SchedulesPanel() {
))}
</ul>
)}
<div className="flex flex-wrap items-center gap-2 rounded-md border border-dashed border-[var(--color-border)] p-2 text-xs">
<div className="flex flex-wrap items-center gap-2 rounded-[var(--radius-card)] border border-dashed border-[var(--color-border)] p-3 text-xs">
<Input
type="text"
value={labelDraft}
@@ -1326,7 +1326,7 @@ function BackupRestorePanel() {
type="button"
onClick={importBundle}
disabled={busy}
className="inline-flex items-center gap-1 rounded-md border border-[var(--color-border)] px-3 py-1.5 text-xs hover:bg-[var(--color-surface)] disabled:opacity-50 max-md:min-h-11 max-md:px-4"
className="inline-flex items-center gap-1 rounded-[var(--radius-card)] border border-[var(--color-border)] px-3 py-1.5 text-xs hover:bg-[var(--color-surface)] disabled:opacity-50 max-md:min-h-11 max-md:px-4"
>
{tr("settings_backup_import", undefined, "Import bundle…")}
</button>
@@ -1400,11 +1400,11 @@ function ThemePicker() {
aria-pressed={theme === o.value}
className={`flex w-full items-start gap-2 rounded-[var(--radius-card)] border p-3 text-left transition-colors ${
theme === o.value
? "border-[var(--color-accent)] bg-[var(--color-surface-raised)] shadow-[var(--shadow-1)]"
: "border-[var(--color-border)] hover:bg-[var(--color-surface-raised)]"
? "border-[var(--glass-edge)] bg-[var(--color-pill)] shadow-[var(--edge-highlight),var(--shadow-1)]"
: "border-[var(--color-border-strong)] hover:bg-[var(--color-surface)]"
}`}
>
<span className="mt-0.5">{o.icon}</span>
<span className="icon-disc !h-7 !w-7" aria-hidden>{o.icon}</span>
<span className="min-w-0 flex-1">
<div className="text-sm font-medium">{o.label}</div>
<div className="text-xs text-[var(--color-muted)]">
@@ -1412,7 +1412,7 @@ function ThemePicker() {
</div>
</span>
{theme === o.value && (
<CheckCircle2 size={12} className="text-[var(--color-good)]" />
<CheckCircle2 size={14} className="shrink-0 text-[var(--color-accent-bright)]" />
)}
</button>
))}
@@ -1441,7 +1441,7 @@ function TextSizePicker() {
{/* Stepper row: each button is a percentage of the designed size. The
buttons themselves sit OUTSIDE the rem flow (fixed text-[13px]) so
they stay tappable even when the user has scaled the app very small. */}
<div className="flex flex-wrap gap-1.5">
<div className="flex flex-wrap gap-2" role="group" aria-label={tr("settings_section_text_size", undefined, "Text size")}>
{UI_SCALE_STEPS.map((step) => {
const active = Math.abs(step - scale) < 0.001;
return (
@@ -1450,11 +1450,7 @@ function TextSizePicker() {
type="button"
onClick={() => setScale(step)}
aria-pressed={active}
className={`min-w-[3rem] rounded-md border px-2 py-1.5 text-center text-[13px] max-md:min-h-11 ${
active
? "border-[var(--color-accent)] bg-[var(--color-surface)] font-semibold"
: "border-[var(--color-border)] hover:bg-[var(--color-surface)]"
}`}
className="chip min-h-8 min-w-[3rem] justify-center px-3 py-1.5 text-[13px] max-md:min-h-11"
>
{uiScaleLabel(step)}
{Math.abs(step - 1) < 0.001 && (
@@ -1661,7 +1657,7 @@ function ResetPanel() {
type="button"
onClick={doReset}
disabled={busy}
className="inline-flex items-center gap-1.5 rounded-md border border-[var(--color-bad)] px-3 py-1.5 text-xs text-[var(--color-bad)] hover:bg-[var(--color-bad)] hover:text-[var(--color-accent-contrast)] disabled:opacity-50 max-md:min-h-11 max-md:px-4"
className="inline-flex items-center gap-1.5 rounded-[var(--radius-card)] border border-[var(--color-bad)] px-3 py-1.5 text-xs text-[var(--color-bad)] hover:bg-[var(--color-bad)] hover:text-[var(--color-accent-contrast)] disabled:opacity-50 max-md:min-h-11 max-md:px-4"
>
{busy ? (
<Spinner size={12} tone="inherit" />
@@ -1747,7 +1743,7 @@ function UpdatesPanel() {
/>
)}
{phase.kind === "downloaded" && (
<div className="rounded-md border border-[var(--color-good)] bg-[var(--color-surface)] p-3 text-xs">
<div className="rounded-[var(--radius-card)] border border-[var(--color-good)] bg-[var(--color-surface)] p-3 text-xs">
<div className="mb-1 font-medium text-[var(--color-good)]">
{tr(
"update_downloaded_title",
@@ -1823,7 +1819,7 @@ function UpdatesPanel() {
type="button"
onClick={handleCheck}
disabled={busy || phase.kind === "downloading"}
className="inline-flex items-center gap-1.5 rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] px-3 py-1.5 text-xs hover:bg-[var(--color-surface-3)] disabled:opacity-50 max-md:min-h-11 max-md:px-4"
className="inline-flex items-center gap-1.5 rounded-[var(--radius-card)] border border-[var(--color-border)] bg-[var(--color-surface)] px-3 py-1.5 text-xs hover:bg-[var(--color-surface-3)] disabled:opacity-50 max-md:min-h-11 max-md:px-4"
>
<RefreshCw size={12} className={busy ? "animate-spin" : ""} />
{tr("check_updates", undefined, "Check for updates")}
@@ -1833,7 +1829,7 @@ function UpdatesPanel() {
type="button"
onClick={handleDownload}
disabled={busy}
className="inline-flex items-center gap-1.5 rounded-md bg-[var(--color-accent)] px-3 py-1.5 text-xs font-medium text-[var(--color-accent-contrast)] hover:opacity-90 disabled:opacity-50"
className="inline-flex items-center gap-1.5 rounded-full bg-[image:var(--accent-fill)] px-3.5 py-1.5 text-xs font-semibold text-[var(--color-accent-contrast)] shadow-[var(--accent-glow)] hover:brightness-105 disabled:opacity-50"
>
<ExternalLink size={12} />
{hasPlatformBundle
@@ -1850,7 +1846,7 @@ function UpdatesPanel() {
type="button"
onClick={handleDownload}
disabled={busy}
className="inline-flex items-center gap-1.5 rounded-md bg-[var(--color-accent)] px-3 py-1.5 text-xs font-medium text-[var(--color-accent-contrast)] hover:opacity-90 disabled:opacity-50"
className="inline-flex items-center gap-1.5 rounded-full bg-[image:var(--accent-fill)] px-3.5 py-1.5 text-xs font-semibold text-[var(--color-accent-contrast)] shadow-[var(--accent-glow)] hover:brightness-105 disabled:opacity-50"
>
<Download size={12} />
{tr(
@@ -2031,7 +2027,7 @@ function ReleaseNotes({
const tr = useTr();
if (!notes.trim()) return null;
return (
<div className="rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] p-3">
<div className="rounded-[var(--radius-card)] border border-[var(--color-border)] bg-[var(--color-surface)] p-3">
<div className="mb-1 flex items-center justify-between text-xs uppercase tracking-wide text-[var(--color-muted)]">
<span>
{tr("whats_new_in", { version }, `What's new in v${version}`)}
+2 -2
View File
@@ -189,7 +189,7 @@ export default function ShellScreen() {
<div className="flex min-h-0 flex-1 flex-col gap-3">
<div
ref={outputRef}
className="flex-1 overflow-auto rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] p-2 font-mono text-xs"
className="flex-1 overflow-auto rounded-[var(--radius-card)] border border-[var(--color-border)] bg-[var(--color-surface)] p-3 font-mono text-xs"
>
{history.length === 0 ? (
<div className="text-[var(--color-muted)]">
@@ -226,7 +226,7 @@ export default function ShellScreen() {
undefined,
"Enter shell command…",
)}
className="flex-1 rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] px-3 py-2 font-mono text-xs outline-none focus:border-[var(--color-accent)] disabled:opacity-50"
className="flex-1 rounded-full border border-[var(--glass-edge)] bg-[var(--color-surface-raised)] px-3 py-2 font-mono text-xs outline-none focus:border-[var(--color-accent)] disabled:opacity-50"
autoFocus
/>
<Button