Stage 3a: wrap the app in 7.css's .window.glass.active frame

The app rendered a bare .title-bar with no .window around it, which is why the
Aero frame, glass and drop shadow were missing and the title-bar controls sat
permanently in their inactive colours: EVERY control-glyph rule in 7.css is
keyed on `.title-bar.active` / `.window.active .title-bar`, and nothing ever
set .active.

- app.html's root is now `.window.glass`, so it picks up the 6px radius, the
  1px frame, the `inset 0 0 0 1px #fff9` highlight, the drop shadow and the
  backdrop-filter glass layer.
- The content below the title bar moved into `.window-body`, 7.css's recessed
  inner panel (6px inset + its own border) — the thing that gives a Win7 window
  its depth.
- `.active` binds to a new `windowFocused` signal, seeded from
  document.hasFocus() and updated from window focus/blur. The webview receives
  those when the NATIVE window's focus changes, so no RPC is needed.

Harness renders the shell focused and unfocused: active shows the red close
button and crisp glyphs, inactive desaturates, matching the reference.

Native window transparency (for the drop shadow and rounded corners to fall
outside the window rather than being clipped by an opaque surface) is
deliberately NOT in this commit — it is a native flag with a real chance of
misbehaving on Linux, so it gets its own change and a real-app check.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-08-16 02:31:27 +02:00
co-authored by Claude
parent 2db367318d
commit 2294453c93
3 changed files with 38 additions and 13 deletions
+13 -4
View File
@@ -51,9 +51,9 @@
</div>
</figure>
<figure class="cell" style="margin:0">
<figcaption>ours — bare .title-bar, no wrapper</figcaption>
<figcaption>ours — app.html shell (active above, inactive below)</figcaption>
<div class="stage">
<div style="width: 320px">
<div class="window glass active" style="width: 320px">
<div class="title-bar">
<div class="title-bar-text">miniSCP</div>
<div class="title-bar-controls">
@@ -62,9 +62,18 @@
<button aria-label="Close"></button>
</div>
</div>
<div style="background: var(--w7-surface); padding: 10px">
<p style="margin:0">No frame, no shadow, no glass.</p>
<div class="window-body"><p style="margin:0">Focused window.</p></div>
</div>
<div class="window glass" style="width: 320px; margin-top: 12px">
<div class="title-bar">
<div class="title-bar-text">miniSCP</div>
<div class="title-bar-controls">
<button aria-label="Minimize"></button>
<button aria-label="Maximize"></button>
<button aria-label="Close"></button>
</div>
</div>
<div class="window-body"><p style="margin:0">Unfocused — chrome desaturates.</p></div>
</div>
</div>
</figure>
+10 -2
View File
@@ -1,4 +1,7 @@
<div class="flex h-screen flex-col bg-background text-foreground">
<!-- .window.glass is 7.css's Aero frame (radius, 1px border, inset highlight,
drop shadow, backdrop blur); .active is what un-greys the title bar and its
control glyphs — every .title-bar-controls rule is keyed on it. -->
<div class="window glass flex h-screen flex-col" [class.active]="windowFocused()">
@if (!rpc.available) {
<div class="flex flex-1 items-center justify-center">
<div class="max-w-sm rounded-lg border border-border bg-card p-6 text-center shadow-sm">
@@ -10,8 +13,12 @@
</div>
</div>
} @else {
<!-- win7 skin only: the window is frameless, so this IS the chrome -->
<!-- the window is frameless, so this IS the chrome -->
<app-title-bar [title]="appName" (closeRequested)="closeWindow()" />
<!-- 7.css's .window-body: the recessed inner panel, with the 6px inset and
its own border that give a Win7 window its depth. -->
<div class="window-body flex min-h-0 flex-1 flex-col overflow-hidden">
<app-tab-bar />
<main class="flex min-h-0 flex-1 divide-x divide-border">
@@ -21,6 +28,7 @@
<app-transfer-bar />
<app-status-bar />
</div>
}
<!-- modal block: the connection-manager window owns the app until closed -->
+8
View File
@@ -45,6 +45,11 @@ import { UpdaterService } from './updater.service';
host: {
'(window:keydown)': 'onWindowKeydown($event)',
'(window:contextmenu)': 'blockContextMenu($event)',
// Aero greys an inactive window's title bar and controls. The webview gets
// DOM focus/blur when the native window's focus changes, so this needs no
// RPC — see the `active` binding on .window in app.html.
'(window:focus)': 'windowFocused.set(true)',
'(window:blur)': 'windowFocused.set(false)',
},
})
export class App {
@@ -62,6 +67,9 @@ export class App {
/** The connection-manager window is open — this window is modal-blocked. */
readonly managerOpen = signal(false);
/** Drives `.window.active`: Aero desaturates an unfocused window's chrome. */
readonly windowFocused = signal(document.hasFocus());
/** Shown in the win7 title bar (no native titlebar to carry it). */
readonly appName = BRANDING.appName;