Hovering the win7 X painted a red mark on the window border's far edge. Diagnosed programmatically, per request, with a new scripts/hover-probe.ts: Firefox headless has no pointer, so the probe copies the built stylesheet with every `:hover` rewritten to a `.simhover` class, renders the app's exact frame markup (w7-frame + direct-child title-bar + aria-label'd controls) with the X force-hovered, and pixel-scans four regions for red-dominant pixels, printing a verdict table and exiting non-zero on artifacts. Verdict from the first run: the right border and the region beside it were CLEAN — the reported mark is the glow escaping over the TOP edge. 7.css's hover ::before carries box-shadow 0 0 7px 3px #e68e75, a child's box-shadow is not clipped by the parent's border or radius, and the caption buttons sit flush against the window's top edge (border-top: 0) — so 100 red pixels landed above the frame: a tint across the top border and a halo floating on the desktop past it. Fix: clip-path inset(0 0 -1rem 0) on `.w7-frame > .title-bar` in skinfix. The glow now dies at the frame border — where the real Win7 DWM stopped it — and the bottom stays open so the authentic bleed toward the caption survives. clip-path clips hit-testing too, but only outside the region; the drag area is the title-bar's own box, entirely inside. Scoped to w7-frame so harness and demo windows keep stock 7.css behaviour. Verified: probe verdict now clean (escape regions 0, the intended in-frame glow still measures 30 px — the effect survived, only the escape is gone); the resting-state harness diffs exactly zero, so nothing but hover pixels changed; tsc + build clean. The probe stays in scripts/ as the regression test; gotchas.md records both the clipping rule and the :hover -> class technique. Co-Authored-By: Claude <noreply@anthropic.com>
759 lines
35 KiB
CSS
759 lines
35 KiB
CSS
/* The SCOPED 7.css build: postcss-prefix-selector rewrote every selector to
|
|
`.win7` (root rules) or `.win7 <sel>` (the rest), so the whole framework is
|
|
inert until the `.win7` class is on <html> — written once by buildPreload
|
|
for the skin this window booted, and never changed after. All 61
|
|
icons are data URIs, so nothing needs copying into assets — and NOT the
|
|
`7.inline.css` build, which flattens away the --w7-* vars the dark palette
|
|
below depends on. The deep path is required: 7.css ships no `exports` map
|
|
and Tailwind's resolver only reads `style`/`mainFields`, so a bare
|
|
`@import "7.css"` finds no entry. */
|
|
@import "7.css/dist/7.scoped.css" layer(skin);
|
|
|
|
/* ==========================================================================
|
|
Windows 7 skin — buildPreload puts `.win7` on <html> at first paint
|
|
==========================================================================
|
|
|
|
Three tiers, and the split matters:
|
|
|
|
- `@layer skin` (the 7.css import at the top of this file) owns the
|
|
CONTROLS: buttons, inputs, tabs, progress bars, tree views, the Aero
|
|
title bar. It sits ABOVE `utilities`, so a bare `button` rule in 7.css
|
|
beats `.rounded-md` on that same button. That inversion is the whole
|
|
trick — it restyles ~1900 inline utility classes with no template edits.
|
|
- This unlayered section owns the BRIDGE: it re-points the app's own
|
|
semantic tokens at Win7 values, so utilities Tailwind still controls
|
|
(bg-card, border-border, text-muted-foreground) also come out
|
|
Win7-correct. It adds NO new tokens on purpose — a new colour token
|
|
would need a matching `--color-*` line in `@theme inline`.
|
|
- `@layer skinfix` (last, above `skin`) hands individual properties BACK to
|
|
the utilities layer with `revert-layer`, for the places where 7.css's
|
|
Win7-dialog metrics are wrong for this app.
|
|
|
|
WARNING for future edits: because `skin` outranks `utilities`, any Tailwind
|
|
utility added later to a <button>, <input>, <select>, <textarea> or <table>
|
|
silently loses under this skin. `skinfix` is the only place to fix that.
|
|
|
|
Specificity notes — the blocks below must beat the token blocks above:
|
|
:root.win7 (0,2,0) beats base :root (0,1,0)
|
|
:root.win7[data-theme='dark'] (0,3,0) beats :root[data-theme=…] (0,2,0)
|
|
and ties the darker-tone block (0,3,0),
|
|
winning on source order (it is last).
|
|
The accent formula blocks are deliberately NOT overridden: they only set
|
|
--primary/--ring/--cursor-tint, and letting them through is what makes the
|
|
accent picker recolour the Aero chrome — authentic, Win7 shipped exactly
|
|
that ("Window Color" personalization). */
|
|
|
|
/* Segoe UI is not on macOS/Linux and is not redistributable. Noto Sans is the
|
|
substitute 7.css itself names in its --w7-font fallback stack. Vendored
|
|
(OFL-1.1) from @fontsource/noto-sans@5.3.0, files/noto-sans-latin-{400,700}
|
|
-normal.woff2 -> ./fonts/, i.e. INSIDE this skin: the font exists only for
|
|
the Win7 look, and keeping it here means the url() below stays relative to
|
|
this file wherever the skin folder moves. (It used to sit at ui/src/fonts/
|
|
next to the old single stylesheet — splitting this file out rebased the
|
|
url() three directories deeper and would have silently 404'd the font.)
|
|
Vendored rather than @imported from node_modules because Tailwind inlines
|
|
imported CSS, which would leave @fontsource's relative url() rebased against
|
|
the wrong directory. They live under src/ (not public/) so esbuild emits them
|
|
as hashed assets and rewrites the url() — a public/ path is not resolvable at
|
|
build time. */
|
|
@font-face {
|
|
font-family: 'Noto Sans App';
|
|
font-style: normal;
|
|
font-weight: 400;
|
|
font-display: block;
|
|
src: url('./fonts/noto-sans-latin-400-normal.woff2') format('woff2');
|
|
}
|
|
@font-face {
|
|
font-family: 'Noto Sans App';
|
|
font-style: normal;
|
|
font-weight: 700;
|
|
font-display: block;
|
|
src: url('./fonts/noto-sans-latin-700-normal.woff2') format('woff2');
|
|
}
|
|
|
|
:root.win7 {
|
|
/* Real Segoe first (Windows), bundled substitute everywhere else. */
|
|
--w7-family: 'Segoe UI', 'SegoeUI', 'Noto Sans App', system-ui, sans-serif;
|
|
/* ~7.css's 9pt, in REM so it follows the interface scale (the root
|
|
font-size lever) like every other size in the app — 0.75rem = 12px at the
|
|
16px root. Restating the `font` SHORTHAND (not just the family) is
|
|
deliberate: 7.css applies `font: var(--w7-font)` on 15 component rules,
|
|
and the shorthand also resets weight and line-height — pinning 400/1.4
|
|
here stops those rules from flattening the app's `font-medium` / leading. */
|
|
--w7-fs: 0.75rem;
|
|
--w7-font: 400 var(--w7-fs) / 1.4 var(--w7-family);
|
|
|
|
/* Surface ramp, Win7 Explorer. This INVERTS the modern ramp's direction:
|
|
there --chrome (frame strips) sits below --background (content panes);
|
|
here the content pane is the brightest thing on screen and the frame is
|
|
the #f0f0f0 window surface. Deliberate — that is what Explorer was. */
|
|
--background: #ffffff;
|
|
--chrome: #f0f0f0;
|
|
--card: #f0f0f0;
|
|
--foreground: #000000;
|
|
--card-foreground: #000000;
|
|
--muted-foreground: #6d6d6d;
|
|
--secondary: #e8e8e8;
|
|
--secondary-foreground: #000000;
|
|
--muted: #e8e8e8;
|
|
/* Win7's hover is a pale blue wash, not a grey — matches --w7-el-grad-h */
|
|
--accent: #eaf6fd;
|
|
--accent-foreground: #000000;
|
|
--border: #d5d5d5;
|
|
--input: #abadb3; /* 7.css's own text-input border */
|
|
--destructive: #c62828;
|
|
--destructive-foreground: #ffffff;
|
|
--success: #06b025; /* the Win7 progress-bar green */
|
|
|
|
/* 0.3125rem = 5px at the 16px root — NOT 3px: `rounded-md` is 74 of the 112
|
|
radius utilities in the templates and compiles to
|
|
calc(var(--radius) - 0.125rem), so 5 lands it exactly on --w7-el-bdr (3px).
|
|
--radius itself feeds `rounded-lg` (12 uses) at 5px, near the 6px window
|
|
radius. Bare `rounded` (26 uses) is a Tailwind literal 0.25rem. Rem so the
|
|
corners follow the interface scale with everything else. */
|
|
--radius: 0.3125rem;
|
|
}
|
|
|
|
/* Light values for every 7.css literal the skinfix rules below restate through
|
|
vars (7.css hardcodes these outside its var system). The values are 7.css's
|
|
own, verbatim — light must render exactly as stock. Dark overrides live in
|
|
the duplicated dark blocks above, which is the house pattern: rules are
|
|
written ONCE and theme through tokens. (The previous attribute-keyed dark
|
|
rules silently missed SYSTEM-dark — the media-query path sets no attribute.) */
|
|
:root.win7 {
|
|
/* ListView (listview.css) */
|
|
--w7-lv-bg: #fff;
|
|
--w7-lv-bd: #c0c1cd;
|
|
--w7-lv-c: #000;
|
|
--w7-lv-head-grad: linear-gradient(180deg, #fff 45%, #fafafa 0, #f0f0f0);
|
|
--w7-lv-head-bd: #d7d7d7;
|
|
--w7-lv-cell-bd: #eee;
|
|
/* Tabs (tabs.css). 7.css hardcodes `color: #222` on every tablist button,
|
|
outside its own var system, which is unreadable on the dark surface. */
|
|
--w7-tab-c: #222;
|
|
/* Menu (menu.css) */
|
|
--w7-mn-c: #000;
|
|
--w7-mn-bd: #0006;
|
|
--w7-mn-stripe: inset 1px 0 #00000026, inset -1px 0 #fff;
|
|
--w7-mn-divider: inset 0 1px #00000026, inset 0 -1px #fff;
|
|
--w7-mn-check-ring: #b3d3f9;
|
|
--w7-mn-check-c: #0c12a1;
|
|
/* Glass caption streaks (window.css .window.glass>.title-bar): a literal.
|
|
The two #fff5 corner shines wash a DARK caption silver, so dark subdues
|
|
every white in the stack while keeping the streak structure. */
|
|
--w7-glass-title: linear-gradient(135deg, #fff5 70px, transparent 100px), linear-gradient(225deg, #fff5 70px, transparent 100px), linear-gradient(54deg, #0002 0 4%, #6661 6% 6%, #0002 8% 10%, #0002 15% 16%, #aaa1 17% 18%, #0002 23% 24%, #bbb2 25% 26%, #0002 31% 33%, #0002 34% 34.5%, #bbb2 36% 40%, #0002 41% 41.5%, #bbb2 44% 45%, #bbb2 46% 47%, #0002 48% 49%, #0002 50% 50.5%, #0002 56% 56.5%, #bbb2 57% 63%, #0002 67% 69%, #bbb2 69.5% 70%, #0002 73.5% 74%, #bbb2 74.5% 79%, #0002 80% 84%, #aaa2 85% 86%, #0002 87%, #bbb1 90%) 0 / 100vw 100vh no-repeat fixed;
|
|
/* Caption text (window.css .title-bar-text: black + 8-fold white halo) */
|
|
--w7-cap-c: #000;
|
|
--w7-cap-glow: 0 0 10px #fff, 0 0 10px #fff, 0 0 10px #fff, 0 0 10px #fff,
|
|
0 0 10px #fff, 0 0 10px #fff, 0 0 10px #fff, 0 0 10px #fff;
|
|
}
|
|
|
|
/* Hand-authored dark Win7. Microsoft never shipped one — this exists so the
|
|
theme and tone settings stay live under the skin. Only the colour-carrying
|
|
--w7-* vars are listed: --w7-el-grad and --w7-w-grad are DERIVED from the
|
|
values below and recolour for free, while --w7-el-grad-h/-a and
|
|
--w7-li-bg-hl are literal hex in 7.css and so must be restated. The ~25
|
|
geometry vars and ~20 base64 icon vars are theme-independent.
|
|
DUPLICATED for forced-dark and system-dark, per the rule the token blocks
|
|
above set: every dark value goes in BOTH. */
|
|
:root.win7[data-theme='dark'] {
|
|
--w7-surface: #2b2b2b;
|
|
--w7-el-bg: #3a3a3a;
|
|
--w7-el-bg-d: #333333;
|
|
--w7-el-bg-s-1: #333333;
|
|
--w7-el-bg-s-2: #262626;
|
|
--w7-el-bd: #565656;
|
|
--w7-el-bd-h: #4a8cbe;
|
|
--w7-el-bd-a: #3f6c8c;
|
|
--w7-el-bd-d: #3f3f3f;
|
|
--w7-el-c: #f0f0f0;
|
|
--w7-el-c-d: #6f6f6f;
|
|
--w7-el-sd: inset 0 0 0 1px #ffffff14;
|
|
--w7-el-grad-h: linear-gradient(#3c5a6b 45%, #315062 45%, #2b4557);
|
|
--w7-el-grad-a: linear-gradient(#2c4657, #24384a 30% 50%, #1d2f3f 50%, #16242f);
|
|
--w7-li-bd-hl: #3f6c8c;
|
|
--w7-li-bg-hl: linear-gradient(#ffffff14, #4a6b8433 90%, #ffffff1a);
|
|
--w7-w-bd: #000000e6;
|
|
/* Ares-style black glass: near-black ground; 7.css's own alpha streaks and
|
|
highlight gradients become the gloss. Blue lives in accents (selection,
|
|
active tab), not in the glass itself. */
|
|
--w7-w-bg: #101013;
|
|
--w7-tab-bg: #1c1c1e;
|
|
--w7-title-c: #f5f5f5;
|
|
--w7-link-c: #6fb3ff;
|
|
/* literal restatements consumed by the skinfix rules (see :root.win7) */
|
|
--w7-lv-bg: var(--w7-el-bg-s-2);
|
|
--w7-lv-bd: var(--w7-el-bd-d);
|
|
--w7-lv-c: var(--w7-el-c);
|
|
--w7-lv-head-grad: linear-gradient(180deg, var(--w7-el-bg) 45%, var(--w7-el-bg-d) 0, var(--w7-el-bg-s-1));
|
|
--w7-lv-head-bd: var(--w7-el-bd-d);
|
|
--w7-lv-cell-bd: var(--w7-el-bd-d);
|
|
--w7-tab-c: var(--w7-el-c);
|
|
--w7-mn-c: var(--w7-el-c);
|
|
--w7-mn-bd: var(--w7-el-bd-d);
|
|
--w7-mn-stripe: inset 1px 0 #00000059, inset -1px 0 #ffffff14;
|
|
--w7-mn-divider: inset 0 1px #00000059, inset 0 -1px #ffffff14;
|
|
--w7-mn-check-ring: var(--w7-li-bd-hl);
|
|
--w7-mn-check-c: var(--w7-el-c);
|
|
--w7-glass-title: linear-gradient(135deg, #ffffff1a 70px, transparent 100px), linear-gradient(225deg, #ffffff1a 70px, transparent 100px), linear-gradient(54deg, #0003 0 4%, #ffffff08 6% 6%, #0003 8% 10%, #0003 15% 16%, #ffffff0d 17% 18%, #0003 23% 24%, #ffffff10 25% 26%, #0003 31% 33%, #0003 34% 34.5%, #ffffff10 36% 40%, #0003 41% 41.5%, #ffffff10 44% 45%, #ffffff10 46% 47%, #0003 48% 49%, #0003 50% 50.5%, #0003 56% 56.5%, #ffffff10 57% 63%, #0003 67% 69%, #ffffff10 69.5% 70%, #0003 73.5% 74%, #ffffff10 74.5% 79%, #0003 80% 84%, #ffffff0d 85% 86%, #0003 87%, #ffffff0a 90%) 0 / 100vw 100vh no-repeat fixed;
|
|
/* Ares: white caption on black glass, dark halo instead of white */
|
|
--w7-cap-c: var(--w7-title-c);
|
|
--w7-cap-glow: 0 0 8px #000c, 0 0 8px #000c, 0 1px 2px #000;
|
|
--w7-link-c-h: #9acbff;
|
|
|
|
--background: #1f1f1f;
|
|
--chrome: #2b2b2b;
|
|
--card: #2b2b2b;
|
|
--foreground: #f0f0f0;
|
|
--card-foreground: #f0f0f0;
|
|
--muted-foreground: #a0a0a0;
|
|
--secondary: #383838;
|
|
--secondary-foreground: #f0f0f0;
|
|
--muted: #383838;
|
|
--accent: #2f4a5c;
|
|
--accent-foreground: #f0f0f0;
|
|
--border: #3c3c3c;
|
|
--input: #55585c;
|
|
}
|
|
@media (prefers-color-scheme: dark) {
|
|
:root.win7:not([data-theme='light']) {
|
|
--w7-surface: #2b2b2b;
|
|
--w7-el-bg: #3a3a3a;
|
|
--w7-el-bg-d: #333333;
|
|
--w7-el-bg-s-1: #333333;
|
|
--w7-el-bg-s-2: #262626;
|
|
--w7-el-bd: #565656;
|
|
--w7-el-bd-h: #4a8cbe;
|
|
--w7-el-bd-a: #3f6c8c;
|
|
--w7-el-bd-d: #3f3f3f;
|
|
--w7-el-c: #f0f0f0;
|
|
--w7-el-c-d: #6f6f6f;
|
|
--w7-el-sd: inset 0 0 0 1px #ffffff14;
|
|
--w7-el-grad-h: linear-gradient(#3c5a6b 45%, #315062 45%, #2b4557);
|
|
--w7-el-grad-a: linear-gradient(#2c4657, #24384a 30% 50%, #1d2f3f 50%, #16242f);
|
|
--w7-li-bd-hl: #3f6c8c;
|
|
--w7-li-bg-hl: linear-gradient(#ffffff14, #4a6b8433 90%, #ffffff1a);
|
|
--w7-w-bd: #000000e6;
|
|
--w7-w-bg: #101013;
|
|
--w7-tab-bg: #1c1c1e;
|
|
--w7-title-c: #f5f5f5;
|
|
--w7-link-c: #6fb3ff;
|
|
/* literal restatements consumed by the skinfix rules (see :root.win7) */
|
|
--w7-lv-bg: var(--w7-el-bg-s-2);
|
|
--w7-lv-bd: var(--w7-el-bd-d);
|
|
--w7-lv-c: var(--w7-el-c);
|
|
--w7-lv-head-grad: linear-gradient(180deg, var(--w7-el-bg) 45%, var(--w7-el-bg-d) 0, var(--w7-el-bg-s-1));
|
|
--w7-lv-head-bd: var(--w7-el-bd-d);
|
|
--w7-lv-cell-bd: var(--w7-el-bd-d);
|
|
--w7-tab-c: var(--w7-el-c);
|
|
--w7-mn-c: var(--w7-el-c);
|
|
--w7-mn-bd: var(--w7-el-bd-d);
|
|
--w7-mn-stripe: inset 1px 0 #00000059, inset -1px 0 #ffffff14;
|
|
--w7-mn-divider: inset 0 1px #00000059, inset 0 -1px #ffffff14;
|
|
--w7-mn-check-ring: var(--w7-li-bd-hl);
|
|
--w7-mn-check-c: var(--w7-el-c);
|
|
--w7-glass-title: linear-gradient(135deg, #ffffff1a 70px, transparent 100px), linear-gradient(225deg, #ffffff1a 70px, transparent 100px), linear-gradient(54deg, #0003 0 4%, #ffffff08 6% 6%, #0003 8% 10%, #0003 15% 16%, #ffffff0d 17% 18%, #0003 23% 24%, #ffffff10 25% 26%, #0003 31% 33%, #0003 34% 34.5%, #ffffff10 36% 40%, #0003 41% 41.5%, #ffffff10 44% 45%, #ffffff10 46% 47%, #0003 48% 49%, #0003 50% 50.5%, #0003 56% 56.5%, #ffffff10 57% 63%, #0003 67% 69%, #ffffff10 69.5% 70%, #0003 73.5% 74%, #ffffff10 74.5% 79%, #0003 80% 84%, #ffffff0d 85% 86%, #0003 87%, #ffffff0a 90%) 0 / 100vw 100vh no-repeat fixed;
|
|
/* Ares: white caption on black glass, dark halo instead of white */
|
|
--w7-cap-c: var(--w7-title-c);
|
|
--w7-cap-glow: 0 0 8px #000c, 0 0 8px #000c, 0 1px 2px #000;
|
|
--w7-link-c-h: #9acbff;
|
|
|
|
--background: #1f1f1f;
|
|
--chrome: #2b2b2b;
|
|
--card: #2b2b2b;
|
|
--foreground: #f0f0f0;
|
|
--card-foreground: #f0f0f0;
|
|
--muted-foreground: #a0a0a0;
|
|
--secondary: #383838;
|
|
--secondary-foreground: #f0f0f0;
|
|
--muted: #383838;
|
|
--accent: #2f4a5c;
|
|
--accent-foreground: #f0f0f0;
|
|
--border: #3c3c3c;
|
|
--input: #55585c;
|
|
}
|
|
}
|
|
|
|
/* Dark tone "darker": Aero has no meaningful true black, so rather than leave
|
|
the Tone row a dead control the skin just goes a step deeper. Duplicated
|
|
forced + system like everything else. */
|
|
:root.win7[data-theme='dark'][data-dark-tone='darker'] {
|
|
--w7-surface: #1a1a1a;
|
|
--w7-el-bg: #262626;
|
|
--w7-w-bg: #060607;
|
|
--background: #121212;
|
|
--chrome: #1a1a1a;
|
|
--card: #1a1a1a;
|
|
}
|
|
@media (prefers-color-scheme: dark) {
|
|
:root.win7:not([data-theme='light'])[data-dark-tone='darker'] {
|
|
--w7-surface: #1a1a1a;
|
|
--w7-el-bg: #262626;
|
|
--w7-w-bg: #060607;
|
|
--background: #121212;
|
|
--chrome: #1a1a1a;
|
|
--card: #1a1a1a;
|
|
}
|
|
}
|
|
|
|
/* Accent -> Aero window colour, so the existing accent picker keeps meaning
|
|
under the skin. --w7-w-bg is the one colour 7.css documents as settable.
|
|
LIGHT ONLY: in dark the glass is Ares black regardless of accent (the accent
|
|
keeps tinting selection etc. through --primary). These arms out-rank the
|
|
accent rule below on specificity — without them it wins the tie on source
|
|
order and dark glass turns accent-coloured. */
|
|
:root.win7[data-accent] {
|
|
--w7-w-bg: var(--primary);
|
|
}
|
|
:root.win7[data-theme='dark'][data-accent] {
|
|
--w7-w-bg: #101013;
|
|
}
|
|
:root.win7[data-theme='dark'][data-dark-tone='darker'][data-accent] {
|
|
--w7-w-bg: #060607;
|
|
}
|
|
@media (prefers-color-scheme: dark) {
|
|
:root.win7:not([data-theme='light'])[data-accent] {
|
|
--w7-w-bg: #101013;
|
|
}
|
|
:root.win7:not([data-theme='light'])[data-dark-tone='darker'][data-accent] {
|
|
--w7-w-bg: #060607;
|
|
}
|
|
}
|
|
|
|
/* Unlayered on purpose: the html/body rule above sets font-family unlayered,
|
|
so a layered override would lose. Family only — NOT font-size: the app's
|
|
rem-based utilities are root-relative, and shifting the root would move
|
|
every line box for no gain. */
|
|
.win7 body {
|
|
font-family: var(--w7-family);
|
|
}
|
|
|
|
/* The win7 window is a FRAMELESS, natively transparent window whose only
|
|
opaque surface is meant to be `.window` — that is the whole premise of the
|
|
6px rounded frame, since the area outside the radius must let the desktop
|
|
through. The unlayered `html, body { background: var(--background) }` above
|
|
broke that premise silently: it painted an opaque rectangle behind the
|
|
frame, so the rounded corners had an opaque ground filling them in and the
|
|
window read as a rectangle with a drawn-on curve.
|
|
Unlayered on purpose, same reason as the font-family rule: the html/body
|
|
background it overrides is itself unlayered, and unlayered declarations beat
|
|
every @layer regardless of specificity — a `skinfix` rule could not touch
|
|
it. Scoped to .win7 so the modern skin keeps its opaque ground. */
|
|
html.win7,
|
|
html.win7 body {
|
|
background: transparent;
|
|
}
|
|
|
|
/* Neutralizers live in `skin`, NOT `skinfix`, and this is load-bearing:
|
|
`revert-layer` rolls a property back to the previous LAYER, so from inside
|
|
`skin` it lands on `utilities` (the Tailwind classes already in the
|
|
templates) — which is the whole point. From `skinfix` it would land back on
|
|
7.css and do nothing. Within `skin` these beat the imported rules on equal
|
|
specificity by source order, since the import is at the top of this file. */
|
|
@layer skin {
|
|
/* 7.css sizes every <button> like a Win7 dialog push button
|
|
(min-width 75px, min-height 23px, padding 0 12px). Most of this app's 84
|
|
buttons are ~24px icon squares sized by utilities, so without this the tab
|
|
bar wraps and the options popover overflows. Hand the BOX MODEL back to
|
|
the utilities layer and keep 7.css's paint. The title bar's own controls
|
|
are matched by a MORE specific 7.css selector (.title-bar-controls button)
|
|
and so keep their Aero glyph metrics untouched. */
|
|
.win7 button {
|
|
min-width: 0;
|
|
min-height: 0;
|
|
padding: revert-layer;
|
|
text-align: revert-layer;
|
|
color: revert-layer;
|
|
}
|
|
|
|
/* 7.css hardcodes #fff / #abadb3 / #222 on form controls and tables, outside
|
|
its own var system. Reverting routes them back through the bridged tokens
|
|
above, which is what makes dark mode nearly free. */
|
|
.win7 input[type='text'],
|
|
.win7 input[type='password'],
|
|
.win7 input[type='number'],
|
|
.win7 input[type='search'],
|
|
.win7 textarea {
|
|
background-color: revert-layer;
|
|
border-color: revert-layer;
|
|
}
|
|
.win7 table {
|
|
background-color: revert-layer;
|
|
color: revert-layer;
|
|
}
|
|
|
|
/* Win7 toolbar buttons are FLAT with a hover glow, not Aero slabs —
|
|
Explorer's command bar worked exactly that way. `w7-toolbar` marks the
|
|
three header rows; a plain `.title-bar` sibling is deliberately NOT one of
|
|
them, so its controls keep the real Aero treatment. `w7-flat` is the
|
|
per-button escape hatch. Both classes are inert under the modern skin.
|
|
`:not([role='tab'])` is load-bearing: the workspace strip IS a w7-toolbar,
|
|
so without the exemption this rule would strip the border, gradient and
|
|
both pseudo-elements off every tab and leave 7.css's tab component drawing
|
|
nothing. Tabs want the full Aero slab; the +/Connections/options buttons
|
|
beside them want the flat treatment.
|
|
Only `background` stays here — it is the one property that needs to land
|
|
on `utilities` via revert-layer. The literals that suppress the Aero slab
|
|
moved to `skinfix`, because 7.css restates border-color at
|
|
`.win7 button:not(:disabled):not(:hover)` = (0,3,1), which outranks
|
|
`.win7 .w7-flat` = (0,2,0). The toolbar arm only ever won that fight by
|
|
tying on specificity and landing later in the same layer — an accident,
|
|
and one w7-flat did not share: it rendered a bordered slab regardless. */
|
|
.win7 .w7-toolbar button:not([role='tab']),
|
|
.win7 .w7-flat {
|
|
background: revert-layer;
|
|
}
|
|
.win7 .w7-toolbar button:not([role='tab'])::before,
|
|
.win7 .w7-toolbar button:not([role='tab'])::after,
|
|
.win7 .w7-flat::before,
|
|
.win7 .w7-flat::after {
|
|
content: none;
|
|
}
|
|
|
|
}
|
|
|
|
/* Above `skin`, for rules that must state a value rather than defer to a
|
|
neighbouring layer. */
|
|
@layer skinfix {
|
|
/* ---- Scale bridge: re-express 7.css's px metrics in rem ----
|
|
7.css sizes everything in px (370 of them, zero rem) and its --w7-font
|
|
shorthand reaches only 15 rules' TYPOGRAPHY — the boxes stay frozen. Under
|
|
the interface scale (root font-size lever) win7 text would grow inside
|
|
23px buttons, 14px table rows and a 15px-line-height caption. Every value
|
|
below is 7.css's own, converted at the 16px root (23px -> 1.4375rem …),
|
|
so scale 1.0 renders pixel-identical to stock and every other scale keeps
|
|
the boxes proportional to the text.
|
|
Deliberately NOT converted: 1px hairlines and focus rects (the tr border
|
|
that stops selection shifting rows, .is-cursor's 1px dotted outline, menu
|
|
bevels, the frame's inset glass ring) — those stay crisp at any scale. */
|
|
|
|
/* 7.css's own size vars — defined on .win7 in px; rem lands on the same
|
|
pixels at scale 1. Covers checkbox/radio/slider/tooltip/treeview/menu
|
|
gutters and both window metrics in one shot. */
|
|
.win7 {
|
|
--w7-cb-size: 0.875rem; /* 14px */
|
|
--w7-rd-size: 0.875rem;
|
|
--w7-rdd-size: 0.5rem; /* 8px */
|
|
--w7-rdl-space: 0.375rem; /* 6px */
|
|
--w7-sdt-w: 0.625rem; /* 10px */
|
|
--w7-sdt-h: 1.125rem; /* 18px */
|
|
--w7-blt-size: 1.125rem;
|
|
--w7-tvb-size: 0.5rem;
|
|
--w7-tv-left: 1.25rem; /* 20px */
|
|
--w7-clc-size: 0.3125rem; /* 5px */
|
|
--w7-lvc-size: 0.3125rem;
|
|
--w7-mn-left: 1.75rem; /* 28px */
|
|
--w7-el-bdr: 0.1875rem; /* 3px */
|
|
--w7-w-space: 0.375rem; /* 6px */
|
|
--w7-w-bdr: 0.375rem;
|
|
}
|
|
|
|
/* The hardcoded box metrics the vars do not reach, restated in rem; skinfix
|
|
outranks skin by layer, so these win over 7.css.
|
|
NO blanket `.win7 button` rule here, deliberately: the `skin`-layer
|
|
neutralizer strips 7.css's 75x23 metrics off every button (most are icon
|
|
squares sized by utilities), and a skinfix restatement would outrank that
|
|
neutralizer by LAYER and re-inflate all of them. The only buttons that
|
|
legitimately wear Win7 push-button metrics are .w7-btn (opted in below)
|
|
and the title-bar controls (which beat the neutralizer on specificity
|
|
inside `skin` and are restated here). */
|
|
.win7 input[type='email'],
|
|
.win7 input[type='number'],
|
|
.win7 input[type='password'],
|
|
.win7 input[type='search'],
|
|
.win7 input[type='text'],
|
|
.win7 input[type='url'] {
|
|
height: 1.4375rem;
|
|
}
|
|
.win7 input[type='email'],
|
|
.win7 input[type='number'],
|
|
.win7 input[type='password'],
|
|
.win7 input[type='search'],
|
|
.win7 input[type='text'],
|
|
.win7 input[type='url'],
|
|
.win7 textarea {
|
|
padding: 0.1875rem 0.25rem 0.3125rem; /* 3px 4px 5px */
|
|
}
|
|
.win7 table > thead > tr > * {
|
|
height: 1.375rem; /* 22px */
|
|
padding: 0 0.5rem; /* 0 8px */
|
|
}
|
|
.win7 table > tbody > tr > * {
|
|
height: 0.875rem; /* 14px */
|
|
padding: 0.125rem 0.5rem; /* 2px 8px */
|
|
}
|
|
.win7 .title-bar-text {
|
|
line-height: 0.9375rem; /* 15px */
|
|
}
|
|
.win7 .title-bar-controls button {
|
|
min-height: 1.1875rem; /* 19px */
|
|
min-width: 1.8125rem; /* 29px */
|
|
padding: 0;
|
|
}
|
|
/* BOTH of 7.css's wide-close selectors, not just .is-close: the app's title
|
|
bars key the buttons on aria-label, and the base rule above outranks the
|
|
`skin`-layer variant BY LAYER — restating only one arm shrank the close
|
|
button from 48px to 29px (caught by the harness pixel diff). */
|
|
.win7 .title-bar-controls button.is-close,
|
|
.win7 .title-bar-controls button[aria-label='Close'] {
|
|
min-width: 3rem; /* 48px */
|
|
}
|
|
.win7 ul[role='menu'] {
|
|
min-width: 9.375rem; /* 150px */
|
|
padding: 0.125rem; /* 2px */
|
|
}
|
|
.win7 ul[role='menu'] > [role='menuitem'] > a,
|
|
.win7 ul[role='menu'] > [role='menuitem'] > button,
|
|
.win7 ul[role='menu'] > [role='menuitem'] > label {
|
|
padding: 0.25rem 0.625rem 0.25rem 2rem; /* 4px 10px 4px 32px */
|
|
}
|
|
.win7 menu[role='tablist'] button {
|
|
padding: 0.125rem 0.375rem; /* 2px 6px */
|
|
}
|
|
/* The raised active tab: 7.css gives it padding-bottom 4px at (0,2,2) inside
|
|
`skin`, but the shorthand above outranks that BY LAYER and would flatten
|
|
the raise — so the variant is restated here too. */
|
|
.win7 menu[role='tablist'] button[aria-selected='true'] {
|
|
padding-bottom: 0.25rem; /* 4px */
|
|
}
|
|
/* The 10pt checkmark glyph in menu checkables — one of five pt sizes in
|
|
7.css that bypass --w7-font entirely. 10pt = 13.333px ≈ 0.8333rem. */
|
|
.win7 ul [role='menuitem'] > input[type='checkbox']:checked + label:after {
|
|
font-size: 0.8333rem;
|
|
}
|
|
|
|
/* Opt back IN to real Win7 push-button metrics, for the dialog and options
|
|
buttons where they are right. Same rem values as the button rule above —
|
|
this class exists to undo the `skin`-layer neutralizer, not to differ. */
|
|
.win7 .w7-btn {
|
|
min-width: 4.6875rem;
|
|
min-height: 1.4375rem;
|
|
padding: 0 0.75rem;
|
|
}
|
|
|
|
/* ---- ListView (file panels) ----
|
|
7.css's listview.css hardcodes light literals (#fff, #c0c1cd, #eee and the
|
|
white header gradient) instead of --w7-* vars, so unlike the rest of the
|
|
skin it does NOT follow the hand-authored dark palette — the header labels
|
|
came out invisible, dark on dark. These restate the same rules in dark
|
|
terms; light keeps 7.css's own values untouched.
|
|
|
|
CRITICAL: the base surface goes on `table`, NEVER on `tbody > tr > *`.
|
|
7.css paints the selection band on the ROW, and a cell background paints
|
|
over a row background — setting one erased the selection highlight
|
|
entirely in dark mode. */
|
|
.win7 table {
|
|
background-color: var(--w7-lv-bg);
|
|
border-color: var(--w7-lv-bd);
|
|
color: var(--w7-lv-c);
|
|
}
|
|
.win7 table > thead > tr > * {
|
|
background: var(--w7-lv-head-grad);
|
|
border-color: var(--w7-lv-head-bd);
|
|
color: var(--w7-lv-c);
|
|
}
|
|
.win7 table > tbody > tr > :not(:last-child) {
|
|
border-right-color: var(--w7-lv-cell-bd);
|
|
}
|
|
/* 7.css drops the cell separators inside a selected row; the rule above is
|
|
more specific than its, so restore that behaviour explicitly. */
|
|
.win7 table > tbody > tr.highlighted > :not(:last-child) {
|
|
border-right: none;
|
|
}
|
|
.win7 table > tbody > tr.highlighted {
|
|
background: var(--w7-li-bg-hl);
|
|
border-color: var(--w7-li-bd-hl);
|
|
}
|
|
|
|
/* ---- Window frame ----
|
|
The native window is transparent (chromeOptions in src/bun/windows.ts), and
|
|
nothing but .window may paint an opaque surface — that is what lets the 6px
|
|
corner radius actually be transparent at the corners. That invariant is NOT
|
|
free: html/body paint an opaque background unlayered, and had to be reset
|
|
to transparent for .win7 (see the rule above @layer skin). Anything else
|
|
that paints full-bleed will fill the corners the same way.
|
|
NO drop shadow, by request: 7.css's `2px 2px 10px 1px #000000b3` needed a
|
|
margin gutter to fall into and still read heavy against a real desktop, so
|
|
the frame keeps only the inset 1px glass highlight from that shorthand
|
|
(dropping the whole box-shadow would lose the highlight too — it lives in
|
|
the same declaration). No shadow -> no gutter -> full-bleed 100vh.
|
|
Keyed on `w7-frame` — the app hook the three window ROOTS carry — never on
|
|
bare .window: on any other .window (dialogs, demos) this sizing is
|
|
nonsense and 7.css's own shadow is fine. */
|
|
.win7 .window.w7-frame {
|
|
margin: 0;
|
|
height: 100vh;
|
|
box-shadow: inset 0 0 0 1px #fffa;
|
|
}
|
|
|
|
/* A maximized Win7 window has SQUARE corners — the radius only exists to let
|
|
the desktop show past a floating frame, and a rounded maximized window
|
|
leaves four notches of desktop in the screen corners.
|
|
Read off the title bar's own `is-maximized` (title-bar.ts publishes it on
|
|
its host) with :has(), so the frame reacts without a second windowState
|
|
subscription. .title-bar is a DIRECT child of .window — app-title-bar
|
|
carries the class on its host element, which is why `>` holds here.
|
|
The radius is declared three times in 7.css — on .window and on both the
|
|
::before tint and the ::after glass layer — so all three must be zeroed or
|
|
a rounded pseudo-element still cuts the corner off the square frame. */
|
|
.win7 .window.w7-frame:has(> .title-bar.is-maximized),
|
|
.win7 .window.w7-frame:has(> .title-bar.is-maximized)::before,
|
|
.win7 .window.w7-frame:has(> .title-bar.is-maximized)::after {
|
|
border-radius: 0;
|
|
}
|
|
|
|
/* Hovering the close button paints 7.css's red glow ::before
|
|
(box-shadow: 0 0 7px 3px #e68e75), and box-shadows of CHILDREN are not
|
|
clipped by the parent's border or radius — the caption buttons sit flush
|
|
against the window's top edge (border-top: 0), so the glow spilled ABOVE
|
|
the frame: a red tint across the top border and a red halo floating on the
|
|
desktop past it (scripts/hover-probe.ts renders the forced-hover state and
|
|
pixel-scans for exactly this; it caught 100 red pixels above the edge).
|
|
Clip the title bar's children to its own box on top/left/right — the glow
|
|
now dies at the frame border, which is where the real Win7 DWM stopped it
|
|
too — while the bottom stays open (-1rem) so the glow's bleed toward the
|
|
caption below survives; that part is authentic.
|
|
clip-path also clips HIT-TESTING, but only for content outside the region:
|
|
the title-bar's own box (the drag region) is entirely inside it.
|
|
Scoped to w7-frame: demo/harness windows keep stock 7.css behaviour. */
|
|
.win7 .window.w7-frame > .title-bar {
|
|
clip-path: inset(0 0 -1rem 0);
|
|
}
|
|
|
|
/* 7.css's glass layer is `backdrop-filter: blur(4px)` on .window::after —
|
|
but CSS cannot sample the DESKTOP behind a transparent native window
|
|
(there is no backdrop to read), and WebKitGTK paints the unsampleable
|
|
layer BLACK: every see-through part of the glass — the frame ring, the
|
|
area around the title-bar buttons — composited over black instead of over
|
|
the desktop. With it off, the real desktop shows through the glass tint,
|
|
which is closer to Aero than a blur we cannot have. (True blur-behind
|
|
needs compositor support; GNOME offers none to X11/XWayland clients.) */
|
|
.win7 .window.glass::after {
|
|
-webkit-backdrop-filter: none;
|
|
backdrop-filter: none;
|
|
}
|
|
|
|
/* Constant row geometry. 7.css gives ONLY tr.highlighted a 1px border, and
|
|
the table is border-collapse: collapse — so a selected row physically grew
|
|
1px at top and bottom and shoved every row below it down by 2px as the
|
|
cursor moved. Every row reserves that border instead, transparent when not
|
|
selected, so selecting changes colour and nothing else.
|
|
NOTE: this sits in `skinfix`, which outranks `skin` by LAYER regardless of
|
|
specificity, so it would also blank 7.css's highlight border — both
|
|
selected states therefore restate their border-color below. */
|
|
.win7 table > tbody > tr {
|
|
border: 1px solid transparent;
|
|
}
|
|
.win7 table > tbody > tr.highlighted {
|
|
border-color: var(--w7-li-bd-hl);
|
|
}
|
|
|
|
/* Pane-focus model (commander convention, matching Win7 Explorer):
|
|
the CURSOR ROW always carries the selection band in BOTH panels —
|
|
tr.highlighted covers "selected via Space" AND "cursor row" (the template
|
|
ORs them). What changes with pane focus is the band's strength, never
|
|
whether it exists:
|
|
- focused pane -> strong Aero band (below)
|
|
- unfocused pane -> 7.css's own tr.highlighted, which IS Win7's
|
|
inactive-selection wash — faint by design, not a bug.
|
|
The dotted focus rectangle is NOT the focus signal; it is layered on top
|
|
of the band in the focused pane only, the way Win7 drew the keyboard
|
|
focus rect over the selected item. */
|
|
.win7 table.is-active > tbody > tr.highlighted {
|
|
background: var(--w7-el-grad-h);
|
|
border-color: var(--w7-el-bd-h);
|
|
}
|
|
|
|
/* One dotted rectangle around the ROW; per cell (tr > *) draws a dotted box
|
|
around every cell instead — visibly wrong. */
|
|
.win7 table > tbody > tr.is-cursor {
|
|
outline: 1px dotted var(--w7-el-c);
|
|
outline-offset: -1px;
|
|
}
|
|
|
|
/* ---- Menu (context menu) ----
|
|
The menu itself is pure 7.css — ul[role=menu] > li[role=menuitem], see
|
|
ui/src/app/context-menu.ts for the exact contract. (A previous iteration
|
|
claimed "7.css has no menu component" and hand-built a lookalike here;
|
|
false — the markup was one level off and its selectors never matched.)
|
|
Only menu.css's hardcoded LIGHT literals need restating for the dark
|
|
palette, same as the ListView above: the black `color:initial`, the
|
|
white bevel halves of the gutter stripe and dividers, and the checked-box
|
|
ring (#b3d3f9). */
|
|
.win7 ul[role='menu'] {
|
|
color: var(--w7-mn-c);
|
|
border-color: var(--w7-mn-bd);
|
|
}
|
|
.win7 ul[role='menu']:before {
|
|
box-shadow: var(--w7-mn-stripe);
|
|
}
|
|
.win7 ul [role='menuitem'].has-divider:after {
|
|
box-shadow: var(--w7-mn-divider);
|
|
}
|
|
.win7 ul [role='menuitem'] > input[type] + label:before {
|
|
box-shadow: 0 0 0 1px var(--w7-mn-check-ring);
|
|
}
|
|
.win7 ul [role='menuitem'] > input[type='checkbox']:checked + label:after {
|
|
color: var(--w7-mn-check-c);
|
|
}
|
|
|
|
/* ---- Flat buttons (toolbars + the w7-flat escape hatch) ----
|
|
The paint-suppression half of the rule in `skin` (see there for the split).
|
|
It lives in `skinfix` because 7.css restates border-color at (0,3,1) via
|
|
`.win7 button:not(:disabled):not(:hover)` and again on :hover, which no
|
|
reasonable `.w7-flat` selector outranks — layer order settles it instead
|
|
of a specificity arms race. */
|
|
.win7 .w7-toolbar button:not([role='tab']),
|
|
.win7 .w7-flat {
|
|
border-color: transparent;
|
|
box-shadow: none;
|
|
}
|
|
|
|
/* ---- Tabs (workspace strip) ----
|
|
7.css owns the tab CHROME; these restore the tab CONTENT layout it breaks.
|
|
`menu[role=tablist] button` sets `display: block` at (0,2,1), which
|
|
outranks Tailwind's `.flex` in `utilities` — so the icon and the label
|
|
rendered jammed together with no gap and no truncation. Restate the flex
|
|
box here (skinfix outranks skin) and leave every painted property alone.
|
|
The gap matches the gap-1.5 the template asks for. */
|
|
.win7 menu[role='tablist'] button[role='tab'] {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.375rem;
|
|
}
|
|
|
|
/* tabs.css hardcodes `color: #222` on every tablist button — a light-mode
|
|
literal outside 7.css's var system, which left the labels near-invisible
|
|
on the dark surface. Same failure and same remedy as the ListView and menu
|
|
literals above: route it through a token that both dark blocks override. */
|
|
.win7 menu[role='tablist'] button {
|
|
color: var(--w7-tab-c);
|
|
}
|
|
|
|
/* The workspace strip scrolls horizontally when tabs overflow. Setting
|
|
`overflow-x: auto` forces the computed `overflow-y` from visible to auto
|
|
as well (CSS will not pair visible with a non-visible axis), so the strip
|
|
clips vertically — and 7.css raises the ACTIVE tab with `margin-top: -2px`,
|
|
which put its top border outside the scroll box and silently deleted it.
|
|
Reserve those 2px as padding so the pulled-up tab lands inside the box.
|
|
7.css already reserves the matching 3px on the left (padding-left: 3px)
|
|
for the same tab's -3px left margin; it just never anticipated a scroller.
|
|
Verified by pixel scan: the active tab's top edge must be #8e8f8f, not a
|
|
straight jump from the toolbar grey to the tab fill. */
|
|
.win7 .w7-toolbar menu[role='tablist'] {
|
|
padding-top: 2px;
|
|
}
|
|
|
|
.win7 .window.glass > .title-bar {
|
|
background: var(--w7-glass-title);
|
|
background-attachment: fixed;
|
|
}
|
|
|
|
/* Caption text: 7.css hardcodes black + a white halo; in dark that becomes
|
|
the Ares treatment (white caption, dark halo) through the same vars. */
|
|
.win7 .title-bar-text {
|
|
color: var(--w7-cap-c);
|
|
text-shadow: var(--w7-cap-glow);
|
|
}
|
|
}
|