Add a toggleable Windows 7 (7.css) skin behind a new skin setting

Evaluation branch: a full Aero skin, including frameless windows with our own
title bar, switchable against the existing modern look.

The app already had a four-axis runtime skinning mechanism and zero component
stylesheets, so the skin is one new setting plus one stylesheet section rather
than a parallel UI. The key move is cascade order: 7.css is imported into a
`skin` layer ABOVE Tailwind's `utilities`, so its bare element rules outrank
the ~1900 inline utility classes already in the templates. Template changes are
limited to additive, skin-inert hooks (ARIA roles, `has-scrollbar`,
`w7-toolbar`, `w7-btn`).

- styles.css: explicit layer order, `7.css/dist/7.scoped.css` via `layer(skin)`,
  a token bridge re-pointing the existing semantic tokens at Win7 values (no new
  tokens, so `@theme inline` is untouched), a hand-authored dark Win7 palette,
  and neutralizers that `revert-layer` 7.css's dialog-button metrics back to the
  utility layer. Neutralizers must live inside `skin`, not `skinfix` —
  `revert-layer` only reaches `utilities` from one layer up.
- Frameless chrome: `chromeOptions(skin)` is now the single place either skin's
  frame is decided. `titleBarStyle: "hidden"` on every platform, which is also
  the only way to drop the macOS traffic lights. New `title-bar` component
  mounted by all three window roots.
- Window controls over RPC (minimize/toggle-maximize/close/is-maximized), served
  per window kind so the X reuses each one's existing close path — editors still
  get their dirty prompt. electrobun has no maximize event, so the state is
  derived from `resize`; double-click-to-maximize is implemented here too.
- Font: Noto Sans vendored (OFL-1.1) under ui/src/fonts, since Segoe UI is
  absent off Windows and not redistributable.

Verified: backend `tsc --noEmit` clean, `bun run build:ui` clean (486 kB initial,
under the 500 kB budget), app launches with the skin active and no console
errors. The rendered result has not been eyeballed — screen capture and
accessibility are both denied to the shell on this machine.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-08-14 15:02:27 +02:00
co-authored by Claude
parent fc03fa6216
commit 07825c1bd1
33 changed files with 828 additions and 48 deletions
+2 -2
View File
@@ -60,13 +60,13 @@ const PERMS = ['r', 'w', 'x'] as const;
<div class="mt-4 flex justify-end gap-2">
<button
(click)="cancel()"
class="rounded-md px-3 py-1.5 text-xs font-medium hover:bg-accent"
class="w7-btn rounded-md px-3 py-1.5 text-xs font-medium hover:bg-accent"
>
{{ i18n.t('common.cancel') }}
</button>
<button
(click)="submit()"
class="rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90"
class="w7-btn rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90"
>
{{ i18n.t('common.apply') }}
</button>
+3 -3
View File
@@ -17,7 +17,7 @@ import { FocusService } from '../focus.service';
<div class="mt-4 flex justify-end gap-2">
<button
(click)="cancel()"
class="rounded-md px-3 py-1.5 text-xs font-medium hover:bg-accent"
class="w7-btn rounded-md px-3 py-1.5 text-xs font-medium hover:bg-accent"
>
{{ i18n.t('common.cancel') }}
</button>
@@ -26,8 +26,8 @@ import { FocusService } from '../focus.service';
(click)="submit()"
[class]="
request().destructive
? 'rounded-md bg-destructive px-3 py-1.5 text-xs font-medium text-destructive-foreground hover:bg-destructive/90'
: 'rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90'
? 'w7-btn rounded-md bg-destructive px-3 py-1.5 text-xs font-medium text-destructive-foreground hover:bg-destructive/90'
: 'w7-btn rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90'
"
>
{{ request().confirmLabel }}
+3 -3
View File
@@ -79,7 +79,7 @@ import { FocusService } from '../focus.service';
<div class="mt-4 flex items-center gap-2">
<button
(click)="pick('cancel')"
class="rounded-md px-3 py-1.5 text-xs font-medium text-muted-foreground hover:bg-accent hover:text-foreground"
class="w7-btn rounded-md px-3 py-1.5 text-xs font-medium text-muted-foreground hover:bg-accent hover:text-foreground"
>
{{ i18n.t('common.cancel') }}
</button>
@@ -101,14 +101,14 @@ import { FocusService } from '../focus.service';
}
<button
(click)="pick('skip')"
class="rounded-md px-3 py-1.5 text-xs font-medium hover:bg-accent"
class="w7-btn rounded-md px-3 py-1.5 text-xs font-medium hover:bg-accent"
>
{{ i18n.t('dialogs.skip') }}
</button>
<button
#primaryBtn
(click)="pick('overwrite')"
class="rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90"
class="w7-btn rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90"
>
{{ isDir() ? i18n.t('dialogs.merge') : i18n.t('dialogs.replace') }}
</button>
+2 -2
View File
@@ -33,14 +33,14 @@ import { FocusService } from '../focus.service';
<div class="mt-4 flex justify-end gap-2">
<button
(click)="cancel()"
class="rounded-md px-3 py-1.5 text-xs font-medium hover:bg-accent"
class="w7-btn rounded-md px-3 py-1.5 text-xs font-medium hover:bg-accent"
>
{{ i18n.t('common.cancel') }}
</button>
<button
(click)="submit()"
[disabled]="!value().trim()"
class="rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50"
class="w7-btn rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50"
>
{{ request().confirmLabel }}
</button>