Stage 3: split styles.css into a shared base plus per-skin sheets

The 919-line stylesheet had a clean seam: `main`'s version is 278 lines and
this branch only ever APPENDED to it. Verified that directly — a diff of
main's styles.css against our first 298 lines differs only by the @layer
statement and the 7.css import.

- ui/src/styles.css (296) keeps the shared half: the @layer statement, the
  Tailwind import, the OKLCH tokens, @theme inline, the data-text interface
  sizes, the animations and the accent axis. The @layer statement must stay
  first here — it is one of only two rule types allowed above @import, and it
  is what sorts `skin` above `utilities`.
- app/skins/7like/7like.css (636) takes the whole win7 section plus the
  scoped-7.css import that feeds layer(skin).
- app/skins/shadlike/shadlike.css is near-empty by nature, not by omission:
  that skin IS the shared token system plus inline Tailwind, so there is no
  framework to bridge and no cascade to invert. The file documents why, and
  what does/does not belong in it.

Both are @imported from styles.css, so one bundle still ships both, scoped by
`.win7` exactly as before.

One real trap caught here: the @font-face url() is resolved relative to the
stylesheet, so moving the win7 section three directories deeper would have
silently 404'd Noto Sans — the build stays green and the app just falls back
to a different font. Fixed by moving the fonts INTO the skin (they exist only
for the Win7 look, where Noto Sans substitutes for Segoe UI), which keeps
`./fonts/...` correct wherever the folder goes. Confirmed in the built output:
both woff2 emitted as hashed assets with the url rewritten to ./media/.

Verified: build clean, built CSS keeps `@layer theme,base,components,
utilities,skin,skinfix` and all 7.css selectors, the live window's opaque
interior is pixel-identical to the pre-refactor baseline, and both harness
renders (light and dark) diff to exactly zero. A layer-assignment mistake here
would not be subtle — 7.css would lose to ~1900 Tailwind utilities and the app
would look nothing like this.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-08-16 04:53:57 +02:00
co-authored by Claude
parent 39e019af9f
commit b35c30d5ab
5 changed files with 670 additions and 630 deletions
+636
View File
@@ -0,0 +1,636 @@
/* 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 SettingsService.applySkin puts that class on <html>. 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 — SettingsService.applySkin puts `.win7` on <html>
==========================================================================
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, but in px so it tracks the interface-size axis below.
Restating the `font` SHORTHAND (not just the family) is deliberate:
7.css applies `font: var(--w7-font)` on 12 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: 12px;
--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 */
/* 5px, NOT 3px: `rounded-md` is 74 of the 112 radius utilities in the
templates and compiles to calc(var(--radius) - 2px), 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 and is unaffected. */
--radius: 5px;
}
/* Interface size: Win7 shipped this exact axis ("Smaller / Medium / Larger"),
so scaling is more authentic than disabling it. One indirection covers all
three tiers because var substitution is lazy. The --row-h / --list-font
block further up scales the file list independently and is untouched. */
:root.win7[data-text='compact'] { --w7-fs: 11px; }
:root.win7[data-text='large'] { --w7-fs: 13px; }
/* 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 {
/* Opt back IN to real Win7 push-button metrics, for the dialog and options
buttons where they are right. */
.win7 .w7-btn {
min-width: 75px;
min-height: 23px;
padding: 0 12px;
}
/* ---- 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;
}
/* 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);
}
}
+27
View File
@@ -0,0 +1,27 @@
/* ==========================================================================
shadlike skin — the modern, shadcn-style look
==========================================================================
Intentionally near-empty, and that is not an oversight.
This skin IS the shared foundation: the OKLCH token system, `@theme inline`,
the data-text interface sizes and the accent axis all live in ui/src/styles.css
because both skins consume them, and everything else the modern look needs is
a Tailwind utility written inline in its templates. There is no third-party
framework to bridge and no cascade inversion to undo, so there is simply
nothing left for a skin-level stylesheet to say.
Compare app/skins/7like/7like.css, which is 630 lines: all of it exists to
import 7.css above `utilities`, re-point the app's semantic tokens at Win7
values, and hand individual properties back where 7.css's dialog metrics are
wrong for this app.
This file exists so the folder is self-contained and symmetrical with the
other skin — modern-only rules that should not leak into the win7 cascade
have a home here. Rules that BOTH skins need belong in ui/src/styles.css
instead; anything added here is dead weight under .win7.
Scoping note: 7like scopes itself with the `.win7` class that 7.scoped.css
requires. This skin is the default (no marker class), so any rule added here
must NOT be written so broadly that it also matches under .win7 — prefer
scoping to the component, not to bare elements. */