From fa2cec71366575425bf35c91eaa7c4cfb97e32d0 Mon Sep 17 00:00:00 2001 From: Jokin Date: Fri, 9 Oct 2026 10:19:58 +0200 Subject: [PATCH] Size every icon on TDesign's scale: four rem tokens, whole pixels TDesign draws its glyphs on a 24px grid with a 2px stroke and sizes a component's icon 2px above its text, in whole pixels. The templates had drifted to 12 one-off sizes, most of them fractional (0.9rem = 14.4px), so strokes blurred and neighbours never matched: the tab bar's 14.4px cog beside a 12.8px +, the shortcut rows' 12px + beside an 11.2px reset and 9.6px chip x. Every now takes one of --icon-size-sm (14px: beside 11-12px text, small icon buttons), -md (16px: file rows, window titles, path-bar buttons), -lg (20px) or -xl (24px: lone spinners and status marks). --action-icon goes; architecture.md and gotchas.md carry the rule. Co-Authored-By: Claude --- docs/architecture.md | 7 +++- docs/gotchas.md | 6 ++++ ui/src/app/skins/shadlike/app.html | 4 +-- .../connection-manager.html | 28 +++++++-------- ui/src/app/skins/shadlike/context-menu.ts | 4 +-- .../shadlike/dialogs/overwrite-dialog.ts | 2 +- .../shadlike/editor-window/editor-window.html | 22 ++++++------ .../skins/shadlike/file-panel/file-panel.html | 36 +++++++++---------- .../settings-window/settings-window.html | 12 +++---- .../skins/shadlike/status-bar/status-bar.ts | 8 ++--- ui/src/app/skins/shadlike/tab-bar/tab-bar.ts | 16 ++++----- ui/src/app/skins/shadlike/toasts/toasts.ts | 4 +-- .../shadlike/transfer-bar/transfer-bar.html | 8 ++--- ui/src/styles.css | 14 +++++++- 14 files changed, 97 insertions(+), 74 deletions(-) diff --git a/docs/architecture.md b/docs/architecture.md index b64849f..cf8be50 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -507,7 +507,12 @@ none`, a drawn check / square dot), selects (a `currentColor` caret; use `::-webkit-scrollbar`s (which make macOS scrollbars always visible). `@theme inline` maps tokens to Tailwind utilities (`bg-primary`, `text-icon-folder`, `bg-toolbar-2`, `bg-cursor-tint`). File-type icon colors -come from Oxide's hue scales (800 steps dark, 600 light). **Interface scale** (`settings.uiScale`, +come from Oxide's hue scales (800 steps dark, 600 light). **Icon sizes** follow +TDesign's rule (an icon is 2px larger than its text, in whole pixels) through +four rem tokens, and every `` takes one: `--icon-size-sm` 14px (beside +11–12px text, small icon buttons), `-md` 16px (13–14px text: file rows, window +titles; the path-bar buttons), `-lg` 20px and `-xl` 24px (lone spinners and +status marks). **Interface scale** (`settings.uiScale`, 0.5–4 in 0.1 steps): one root font-size lever — `applyUiScale` sets `html { font-size: 16px * scale }`, and since EVERY size in the app is rem (panel tokens like `--row-h`, all Tailwind utilities, ng-icon sizes), the diff --git a/docs/gotchas.md b/docs/gotchas.md index a9a6799..6d69b03 100644 --- a/docs/gotchas.md +++ b/docs/gotchas.md @@ -76,6 +76,12 @@ touching the adjacent code. moment the pseudo-element is styled, WKWebView draws classic always-visible scrollbars that take layout width. Deliberate here (square design); don't reach for it to "tint" scrollbars without accepting that. +- **An icon size is one of the four `--icon-size-*` tokens, never a one-off + rem.** TDesign glyphs are 24-unit SVGs with 2-unit strokes, so a fractional + size (0.9rem = 14.4px) lands every stroke between device pixels and blurs + it. Per-template picks had drifted to 12 sizes, and neighbours stopped + matching: a 14.4px cog beside a 12.8px +, and a 12px + beside 9.6px × in + the shortcut chips. ## The interface scale (root font-size lever) diff --git a/ui/src/app/skins/shadlike/app.html b/ui/src/app/skins/shadlike/app.html index 56bfe17..c893df7 100644 --- a/ui/src/app/skins/shadlike/app.html +++ b/ui/src/app/skins/shadlike/app.html @@ -2,7 +2,7 @@ @if (!rpc.available) {
- +

{{ i18n.t('app.notConnectedTitle') }}

{{ i18n.t('app.notConnectedBody') }} @@ -28,7 +28,7 @@ (mousedown)="focusManager()" >

- +

{{ i18n.t('app.managerOpen') }}

@@ -36,14 +36,14 @@ [title]="shortcuts.tip(i18n.t('connMgr.newConnectionHint'), 'conn-new')" class="flex items-center gap-1 px-2 py-1 text-xs text-muted-foreground hover:bg-accent hover:text-foreground" > - {{ i18n.t('connMgr.newConnection') }} + {{ i18n.t('connMgr.newConnection') }}
@@ -69,7 +69,7 @@ > @@ -90,14 +90,14 @@ [title]="shortcuts.tip(i18n.t('connMgr.renameStack'), 'conn-rename-stack')" class="grid size-4 place-items-center text-muted-foreground hover:text-foreground" > - + @if (group.connections.length) { }
@@ -123,7 +123,7 @@ [class.bg-accent]="draft()?.id === connection.id" [class.opacity-50]="draggedId() === connection.id" > - + {{ connection.name }} } @empty { @@ -161,7 +161,7 @@ [class.bg-accent]="draft()?.id === connection.id" [class.opacity-50]="draggedId() === connection.id" > - + {{ connection.name }} } @@ -342,7 +342,7 @@ [title]="shortcuts.tip(i18n.t('common.delete'), 'conn-delete')" class="flex items-center gap-1.5 px-2 py-1.5 text-xs text-muted-foreground hover:bg-accent hover:text-destructive" > - {{ i18n.t('common.delete') }} + {{ i18n.t('common.delete') }} @@ -374,7 +374,7 @@ } @else {
- +

{{ i18n.t('connMgr.selectPrompt') }}

diff --git a/ui/src/app/skins/shadlike/context-menu.ts b/ui/src/app/skins/shadlike/context-menu.ts index 13d8cd7..6eae134 100644 --- a/ui/src/app/skins/shadlike/context-menu.ts +++ b/ui/src/app/skins/shadlike/context-menu.ts @@ -77,11 +77,11 @@ const EDGE_GAP = 6; // px on purpose: a viewport-edge margin, not a UI size @if (item.checked) { - + } @else if (item.icon) { } diff --git a/ui/src/app/skins/shadlike/dialogs/overwrite-dialog.ts b/ui/src/app/skins/shadlike/dialogs/overwrite-dialog.ts index de882bc..b5a0aa4 100644 --- a/ui/src/app/skins/shadlike/dialogs/overwrite-dialog.ts +++ b/ui/src/app/skins/shadlike/dialogs/overwrite-dialog.ts @@ -64,7 +64,7 @@ import { FocusService } from '@core/focus.service';
diff --git a/ui/src/app/skins/shadlike/editor-window/editor-window.html b/ui/src/app/skins/shadlike/editor-window/editor-window.html index 5131dca..689ea1a 100644 --- a/ui/src/app/skins/shadlike/editor-window/editor-window.html +++ b/ui/src/app/skins/shadlike/editor-window/editor-window.html @@ -2,7 +2,7 @@ @if (!ctx) {
- +

{{ i18n.t('editor.noFileTitle') }}

{{ i18n.t('editor.noFileBody') }} @@ -20,7 +20,7 @@ [class.h-9]="!isMacChrome" [class.pl-3]="!isMacChrome" > - +

{{ ctx.name }}{{ dirty() ? ' •' : '' }}

@@ -41,13 +41,13 @@ [title]="shortcuts.tip(i18n.t('editor.findHint'), 'find')" class="grid size-6 place-items-center text-muted-foreground electrobun-webkit-app-region-no-drag hover:bg-accent hover:text-foreground" > - + @if (findOpen()) {
- + - +
} @@ -93,19 +93,19 @@
- + {{ i18n.t('editor.disconnected', { name: ctx.sessionTitle }) }}
} @if (loading()) {
- +
} @else if (loadError(); as error) {
- +

{{ i18n.t('editor.cantOpen', { name: ctx.name }) }}

{{ error }}

diff --git a/ui/src/app/skins/shadlike/file-panel/file-panel.html b/ui/src/app/skins/shadlike/file-panel/file-panel.html index 8bcd0ae..2bea1c5 100644 --- a/ui/src/app/skins/shadlike/file-panel/file-panel.html +++ b/ui/src/app/skins/shadlike/file-panel/file-panel.html @@ -10,19 +10,19 @@ [title]="shortcuts.tip(i18n.t('panel.choosePanel'), 'source-picker')" > @if (panel().connecting()) { - + } @else { } {{ isRemote() ? panel().title() : i18n.t('common.local') }}
@@ -34,7 +34,7 @@ [title]="shortcuts.tip(side() === 'left' ? i18n.t('panel.copyToRight') : i18n.t('panel.copyToLeft'), 'send')" class="flex h-(--action-h) shrink-0 items-center px-1 text-muted-foreground hover:bg-accent hover:text-primary" > - + @if (chmodAvailable()) { } @@ -95,7 +95,7 @@ [title]="shortcuts.tip(i18n.t('panel.parentDir'), 'parent')" class="grid size-(--path-ctl-h) shrink-0 place-items-center text-muted-foreground hover:bg-accent hover:text-foreground disabled:opacity-50 disabled:hover:bg-transparent" > - + @@ -117,9 +117,9 @@ class="grid size-(--path-ctl-h) shrink-0 place-items-center text-muted-foreground hover:bg-accent hover:text-foreground disabled:opacity-50 disabled:hover:bg-transparent" > @if (panel().loading()) { - + } @else { - + }
@@ -153,7 +153,7 @@ @if (panel().filter(); as query) {
- + {{ query }} {{ i18n.t('panel.matches', { count: filterMatches() }) }} @@ -163,7 +163,7 @@ [title]="i18n.t('panel.clearFilter')" class="grid size-5 shrink-0 place-items-center text-muted-foreground hover:bg-accent hover:text-foreground" > - +
} @@ -181,7 +181,7 @@ @if (panel().connecting()) {
- + {{ i18n.t('panel.connectingTo', { name: panel().connectingTo() ?? '' }) }} @@ -210,7 +210,7 @@ [class.ring-ring]="i === panel().cursor() && isActive()" >
- + {{ entry.name }}
@for (col of visibleColumns(); track col.key) { @@ -260,7 +260,7 @@
@if (panel().slowLoad() && !panel().connecting()) {
- +
}
diff --git a/ui/src/app/skins/shadlike/settings-window/settings-window.html b/ui/src/app/skins/shadlike/settings-window/settings-window.html index a420b00..56d8c77 100644 --- a/ui/src/app/skins/shadlike/settings-window/settings-window.html +++ b/ui/src/app/skins/shadlike/settings-window/settings-window.html @@ -9,7 +9,7 @@ [class.h-9]="!isMacChrome" [class.pl-3]="!isMacChrome" > - +

{{ i18n.t('options.title') }}

@@ -250,7 +250,7 @@ [title]="i18n.t('options.resetDefault')" class="grid size-5 place-items-center text-muted-foreground hover:bg-accent hover:text-foreground" > - + }
@@ -306,7 +306,7 @@
- + } @@ -387,7 +387,7 @@ class="grid size-5 shrink-0 place-items-center text-muted-foreground hover:bg-accent hover:text-foreground" [class.text-primary]="capturing() === binding.id" > - +
@if (note()?.id === binding.id) { diff --git a/ui/src/app/skins/shadlike/status-bar/status-bar.ts b/ui/src/app/skins/shadlike/status-bar/status-bar.ts index b0f6a6e..de32122 100644 --- a/ui/src/app/skins/shadlike/status-bar/status-bar.ts +++ b/ui/src/app/skins/shadlike/status-bar/status-bar.ts @@ -22,7 +22,7 @@ import { TransfersService } from '@core/transfers.service'; [title]="shortcuts.tip(i18n.t('transfer.showDetails'), 'toggle-transfers')" > @if (transfers.currentJob(); as job) { - + {{ i18n.t('transfer.copying', { name: job.name, percent: percent(job), speed: speedSuffix(job) }) }} @if (transfers.queuedCount(); as queued) { @@ -30,12 +30,12 @@ import { TransfersService } from '@core/transfers.service'; } } @else if (transfers.failedJobs().length; as failed) { - + {{ i18n.t('transfer.failed', { count: failed }) }} } @else if (transfers.hasJobs()) { - + {{ i18n.t('transfer.finished') }} } @else { {{ i18n.t('transfer.ready') }} @@ -46,7 +46,7 @@ import { TransfersService } from '@core/transfers.service';
diff --git a/ui/src/app/skins/shadlike/tab-bar/tab-bar.ts b/ui/src/app/skins/shadlike/tab-bar/tab-bar.ts index 7407d0c..3ded75d 100644 --- a/ui/src/app/skins/shadlike/tab-bar/tab-bar.ts +++ b/ui/src/app/skins/shadlike/tab-bar/tab-bar.ts @@ -53,7 +53,7 @@ import { UpdaterService } from '@core/updater.service'; > @@ -67,7 +67,7 @@ import { UpdaterService } from '@core/updater.service'; [title]="i18n.t('tabBar.closeTab')" class="mr-1 grid size-4 shrink-0 place-items-center text-muted-foreground opacity-0 group-hover:opacity-100 hover:bg-border hover:text-foreground" > - + } @else { @@ -83,7 +83,7 @@ import { UpdaterService } from '@core/updater.service'; [title]="shortcuts.tip(i18n.t('tabBar.newTab'), 'new-tab')" class="ml-0.5 grid size-6 place-items-center text-muted-foreground electrobun-webkit-app-region-no-drag hover:bg-accent hover:text-foreground" > - +
@@ -106,13 +106,13 @@ import { UpdaterService } from '@core/updater.service'; @switch (updater.state()) { @case ('available') { - {{ i18n.t('tabBar.update') }} + {{ i18n.t('tabBar.update') }} } @case ('downloading') { - {{ i18n.t('tabBar.updating') }} {{ updater.progress() }}% + {{ i18n.t('tabBar.updating') }} {{ updater.progress() }}% } @case ('ready') { - {{ i18n.t('tabBar.restartToUpdate') }} + {{ i18n.t('tabBar.restartToUpdate') }} } } @@ -125,7 +125,7 @@ import { UpdaterService } from '@core/updater.service'; [title]="shortcuts.tip(i18n.t('tabBar.manageConnections'), 'connection-manager')" class="grid h-6 px-2 py-1 place-items-center text-[0.6875rem] text-muted-foreground electrobun-webkit-app-region-no-drag hover:bg-accent hover:text-foreground" > - + {{ i18n.t('tabBar.connections') }} @@ -135,7 +135,7 @@ import { UpdaterService } from '@core/updater.service'; [title]="shortcuts.tip(i18n.t('tabBar.options'), 'open-settings')" class="grid size-6 place-items-center text-muted-foreground electrobun-webkit-app-region-no-drag hover:bg-accent hover:text-foreground" > - +
`, diff --git a/ui/src/app/skins/shadlike/toasts/toasts.ts b/ui/src/app/skins/shadlike/toasts/toasts.ts index f829e31..c465f39 100644 --- a/ui/src/app/skins/shadlike/toasts/toasts.ts +++ b/ui/src/app/skins/shadlike/toasts/toasts.ts @@ -17,7 +17,7 @@ import { ToastService } from '@core/toast.service'; > {{ t.text }} @@ -26,7 +26,7 @@ import { ToastService } from '@core/toast.service'; [attr.aria-label]="i18n.t('transfer.dismiss')" class="shrink-0 text-muted-foreground hover:text-foreground" > - +
- {{ i18n.t('transfer.clearFinished') }} + {{ i18n.t('transfer.clearFinished') }}
@for (job of transfers.jobs(); track job.id) {
- + {{ job.name }}
{{ progressLabel(job) }}
@@ -40,7 +40,7 @@ [title]="i18n.t('transfer.cancel')" class="shrink-0 p-1 text-muted-foreground hover:bg-accent hover:text-destructive" > - + } @else { }
diff --git a/ui/src/styles.css b/ui/src/styles.css index eef6aa2..3891301 100644 --- a/ui/src/styles.css +++ b/ui/src/styles.css @@ -197,7 +197,19 @@ --path-ctl-h: 1.75rem; --action-h: 1.5rem; --action-font: 0.6875rem; - --action-icon: 0.9rem; +} + +/* Icon sizes, by TDesign's rule. Its glyphs are drawn on a 24px grid with a + 2px stroke and ship for 16px; its components make an icon 2px larger than + the text beside it, in whole pixels (small button: 12px text, 14px icon). + Every takes one of these four: a one-off size like 0.9rem (14.4px) + puts every stroke between pixels, and one-offs drift until neighbours never + match. Rem like the rest, so the interface scale moves them too. */ +:root { + --icon-size-sm: 0.875rem; /* 14px: beside 11-12px text, and on small icon buttons */ + --icon-size-md: 1rem; /* 16px: beside 13-14px text (file rows, window titles), path-bar buttons */ + --icon-size-lg: 1.25rem; /* 20px: a spinner standing alone */ + --icon-size-xl: 1.5rem; /* 24px: a status mark on a card or an empty pane */ } /* Soft entrance for the panel busy states (connecting placeholder, load overlay). */