From 2b6dfc11fdce0279d577fd89501af234bc153c98 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Jul 2026 23:34:36 +0000 Subject: [PATCH 1/4] bump mine.css from 10.0.3 to 11.0.3 Bumps [mine.css](https://github.com/bcomnes/mine.css) from 10.0.3 to 11.0.3. - [Release notes](https://github.com/bcomnes/mine.css/releases) - [Changelog](https://github.com/bcomnes/mine.css/blob/master/CHANGELOG.md) - [Commits](https://github.com/bcomnes/mine.css/compare/v10.0.3...v11.0.3) --- updated-dependencies: - dependency-name: mine.css dependency-version: 11.0.3 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 14bc9cea..81275b43 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "markdown-it-sup": "^2.0.0", "markdown-it-table-of-contents": "^1.1.0", "markdown-it-task-lists": "^2.1.1", - "mine.css": "^10.0.0", + "mine.css": "^11.0.3", "p-map": "^7.0.2", "package-directory": "^8.1.0", "package-json": "^10.0.0", From 0a9133e7f8d859ff7490ef21fb5d533ecf4d097e Mon Sep 17 00:00:00 2001 From: Bret Comnes Date: Fri, 17 Jul 2026 16:22:44 -0700 Subject: [PATCH 2/4] Migrate DOMStack to mine.css v11 --- .gitignore | 1 + README.md | 2 + browser-tests/cascade-layers.spec.js | 26 + browser-tests/support.js | 71 +++ docs/v12-migration.md | 102 +++- examples/basic/package.json | 2 +- examples/basic/src/global.client.ts | 11 +- examples/basic/src/global.css | 21 +- examples/basic/src/html-page/style.css | 8 +- .../basic/src/js-page/loose-assets/style.css | 3 +- examples/basic/src/js-page/style.css | 128 ++--- examples/basic/src/layouts/root.layout.ts | 1 + examples/basic/src/md-page/style.css | 8 +- examples/basic/src/style.css | 8 +- examples/blog/package.json | 2 +- examples/blog/src/global.client.ts | 6 - examples/blog/src/global.css | 68 +-- examples/blog/src/layouts/post.layout.css | 142 +++--- examples/blog/src/layouts/root.layout.css | 90 ++-- examples/blog/src/layouts/root.layout.ts | 3 +- examples/css-modules/package.json | 2 +- .../css-modules/src/globals/global.client.js | 2 - examples/css-modules/src/globals/global.css | 9 +- .../css-modules/src/layouts/root.layout.js | 1 + examples/css-modules/src/modules/style.css | 2 +- .../markdown-settings/src/global.style.css | 462 ++++++++--------- examples/markdown-settings/src/root.layout.js | 1 + examples/nested-dest/global.css | 10 +- examples/nested-dest/package.json | 2 +- examples/preact-isomorphic/package.json | 2 +- .../src/globals/global.client.ts | 2 - .../preact-isomorphic/src/globals/global.css | 9 +- .../src/isomorphic/style.css | 334 ++++++------- .../preact-isomorphic/src/jsx-page/style.css | 468 +++++++++--------- .../src/layouts/root.layout.ts | 1 + examples/react/package.json | 2 +- examples/react/src/globals/global.client.ts | 13 - examples/react/src/globals/global.css | 211 ++++---- examples/react/src/layouts/root.layout.ts | 1 + examples/string-layouts/src/root.layout.js | 1 + examples/tailwind/src/globals/global.css | 1 + examples/tailwind/src/layouts/root.layout.js | 1 + examples/type-stripping/package.json | 2 +- .../src/globals/global.client.ts | 3 - .../type-stripping/src/globals/global.css | 9 +- .../type-stripping/src/isomorphic/style.css | 460 ++++++++--------- .../type-stripping/src/layouts/root.layout.ts | 1 + examples/uhtml-isomorphic/package.json | 2 +- .../src/globals/global.client.js | 4 - .../uhtml-isomorphic/src/globals/global.css | 9 +- .../src/layouts/root.layout.js | 1 + examples/worker-example/package.json | 2 +- .../worker-example/src/globals/global.css | 118 ++--- .../worker-example/src/layouts/root.layout.js | 1 + examples/worker-example/src/style.css | 176 +++---- .../worker-example/src/worker-page/style.css | 218 ++++---- global.css | 12 +- lib/defaults/default.client.js | 5 +- lib/defaults/default.root.layout.js | 1 + lib/defaults/default.style.css | 9 +- package.json | 2 + playwright.config.js | 24 + style.css | 4 +- test-cases/build-errors/src/style.css | 10 +- test-cases/default-layout/src/global.css | 8 +- test-cases/default-layout/src/style.css | 8 +- test-cases/general-features/src/README.md | 2 + .../src/blog/2023/a-draft-from-2023/style.css | 6 +- .../the-first-blog-post-from-2023/style.css | 6 +- .../src/globals/global.client.js | 5 - .../general-features/src/globals/global.css | 13 +- .../general-features/src/html-page/style.css | 6 +- .../general-features/src/js-page/style.css | 6 +- .../src/layouts/blog.layout.css | 8 +- .../src/layouts/root.layout.css | 7 +- .../general-features/src/md-page/style.css | 82 +-- test-cases/general-features/src/style.css | 7 +- .../src/worker-page/style.css | 78 +-- test-cases/nested-dest/md-page/style.css | 6 +- test-cases/nested-dest/style.css | 8 +- test-cases/page-build-errors/src/style.css | 6 +- 81 files changed, 1938 insertions(+), 1637 deletions(-) create mode 100644 browser-tests/cascade-layers.spec.js create mode 100644 browser-tests/support.js delete mode 100644 examples/blog/src/global.client.ts delete mode 100644 examples/css-modules/src/globals/global.client.js delete mode 100644 examples/preact-isomorphic/src/globals/global.client.ts delete mode 100644 examples/type-stripping/src/globals/global.client.ts delete mode 100644 examples/uhtml-isomorphic/src/globals/global.client.js create mode 100644 playwright.config.js diff --git a/.gitignore b/.gitignore index 02a8f93e..e293d06a 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ package-lock.json public coverage lcov.info +test-results .tap .tmp-* diff --git a/README.md b/README.md index 1b04d23d..8a35587c 100644 --- a/README.md +++ b/README.md @@ -1846,6 +1846,8 @@ Key changes at a glance: - **Default layout**: The bundled and ejected `root.layout.js` now uses [`fragtml`][fragtml]. - **Included dependencies**: `domstack --eject` adds `mine.css`, `fragtml`, and `highlight.js`; Preact, HTM, and `preact-render-to-string` are no longer included for the default template. +- **mine.css v11**: The default styles now use mine.css v11's CSS-only API, browser-controlled color preference, native CSS nesting, and intentional visual changes. Ejected and customized sites must follow the mine.css v11 migration steps. +- **CSS cascade layers**: DOMStack’s default stylesheet establishes the `mine`, `domstack.global`, `domstack.layout`, and `domstack.page` layer order. Following the same layer pattern in custom global, layout, and page stylesheets is recommended for explicit global → layout → page precedence. - **JSX runtime**: Client `.jsx` and `.tsx` bundles still work through esbuild, but DOMStack no longer configures Preact as the default runtime. Install React, Preact, or another runtime in your own project and configure it with `esbuild.settings`. - **Preact examples**: Examples that actually mount Preact in the browser still use Preact and configure it locally. diff --git a/browser-tests/cascade-layers.spec.js b/browser-tests/cascade-layers.spec.js new file mode 100644 index 00000000..1f05ea38 --- /dev/null +++ b/browser-tests/cascade-layers.spec.js @@ -0,0 +1,26 @@ +import { expect, test } from './support.js' + +async function cascadeValue (page) { + return page.getByTestId('cascade-layer-fixture').evaluate(element => { + return getComputedStyle(element).getPropertyValue('--domstack-cascade-layer').trim() + }) +} + +test('orders mine, global, layout, and page cascade layers', async ({ page, siteURL }) => { + await page.goto(`${siteURL}/`, { waitUntil: 'domcontentloaded' }) + await page.addStyleTag({ + content: '@layer mine { .cascade-layer-fixture { --domstack-cascade-layer: mine; } }' + }) + + await expect(page.getByTestId('cascade-layer-fixture')).toHaveCount(1) + await expect.poll(() => cascadeValue(page)).toBe('page') + + await page.locator('link[rel="stylesheet"][href*="style-"]').evaluate(element => element.remove()) + await expect.poll(() => cascadeValue(page)).toBe('layout') + + await page.locator('link[rel="stylesheet"][href*="root.layout-"]').evaluate(element => element.remove()) + await expect.poll(() => cascadeValue(page)).toBe('global') + + await page.locator('link[rel="stylesheet"][href*="global-"]').evaluate(element => element.remove()) + await expect.poll(() => cascadeValue(page)).toBe('mine') +}) diff --git a/browser-tests/support.js b/browser-tests/support.js new file mode 100644 index 00000000..8fd7777d --- /dev/null +++ b/browser-tests/support.js @@ -0,0 +1,71 @@ +import { expect, test as base } from '@playwright/test' +import { readFile } from 'node:fs/promises' +import { createServer } from 'node:http' +import { extname, resolve, sep } from 'node:path' +import { testBuild } from '../index.js' + +const fixtureSrc = resolve(import.meta.dirname, '../test-cases/general-features/src') +const contentTypes = new Map([ + ['.css', 'text/css; charset=utf-8'], + ['.html', 'text/html; charset=utf-8'], + ['.js', 'text/javascript; charset=utf-8'], + ['.woff2', 'font/woff2'] +]) + +export const test = base.extend({ + siteURL: async ({ context }, use) => { + const build = await testBuild(fixtureSrc) + const publicDir = build.dest + const server = createServer(async (request, response) => { + try { + const url = new URL(request.url ?? '/', 'http://127.0.0.1') + const pathname = decodeURIComponent(url.pathname) + const relativePath = pathname.endsWith('/') + ? `${pathname}index.html` + : pathname + const filePath = resolve(publicDir, `.${relativePath}`) + + if (filePath !== publicDir && !filePath.startsWith(`${publicDir}${sep}`)) { + response.writeHead(403).end('Forbidden') + return + } + + const body = await readFile(filePath) + response.writeHead(200, { + 'cache-control': 'no-store', + 'content-type': contentTypes.get(extname(filePath)) ?? 'application/octet-stream' + }) + response.end(body) + } catch (error) { + const status = error?.code === 'ENOENT' ? 404 : 500 + response.writeHead(status).end(status === 404 ? 'Not found' : 'Server error') + } + }) + + await new Promise((resolve, reject) => { + server.once('error', reject) + server.listen(0, '127.0.0.1', () => { + server.off('error', reject) + resolve() + }) + }) + + const address = server.address() + if (!address || typeof address === 'string') throw new Error('Static server did not bind to a TCP port') + + await context.route(/^https?:\/\/(?!127\.0\.0\.1(?::\d+)?(?:\/|$))/, route => route.abort()) + await use(`http://127.0.0.1:${address.port}`) + + for (const page of context.pages()) { + if (!page.isClosed()) await page.close().catch(() => {}) + } + + await new Promise((resolve, reject) => { + server.close(error => error ? reject(error) : resolve()) + server.closeAllConnections() + }) + await build.cleanup() + } +}) + +export { expect } diff --git a/docs/v12-migration.md b/docs/v12-migration.md index 89f1f6aa..e6a4923c 100644 --- a/docs/v12-migration.md +++ b/docs/v12-migration.md @@ -12,7 +12,8 @@ Then apply the v12 changes below. 3. [Default Layout Uses fragtml](#3-default-layout-uses-fragtml) 4. [Keep Layout Dependencies Explicit](#4-keep-layout-dependencies-explicit) 5. [JSX Runtime Is Opt-In](#5-jsx-runtime-is-opt-in) -6. [Migration Checklist](#6-migration-checklist) +6. [mine.css v11 and CSS Cascade Layers](#6-minecss-v11-and-css-cascade-layers) +7. [Migration Checklist](#7-migration-checklist) --- @@ -123,7 +124,96 @@ export default async function esbuildSettingsOverride (esbuildSettings) { --- -## 6. Migration Checklist +## 6. mine.css v11 and CSS Cascade Layers + +DOMStack v12 upgrades its bundled default styles and ejected projects from mine.css v10 to v11. +This is a downstream breaking change even when your project does not import mine.css directly, because the bundled default layout uses it. +Review the complete [mine.css v11 migration guide](https://github.com/bcomnes/mine.css/blob/master/MIGRATION.md) before upgrading a customized or ejected site. + +mine.css v11 is CSS-only. +The package root now resolves to the main stylesheet, and the JavaScript theme switcher is no longer published. +Replace the old deep import with the package root: + +```css +@import 'mine.css'; +``` + +Remove imports of `mine.css` or `mine.css/dist/theme-switcher.js` from JavaScript, calls to `toggleTheme()`, stored theme state, theme controls, and `.light-mode` or `.dark-mode` rules. +Add `` to custom root layouts and use `prefers-color-scheme` for application-specific dark styles. +If you use Highlight.js, load a light theme normally and a dark theme conditionally instead of applying one dark theme in both modes. + +The optional mine.css layout remains a separate import. +DOMStack's default stylesheet imports it explicitly; ejected sites must continue to do the same. + +### DOMStack cascade layer order + +DOMStack v12's default stylesheet establishes this low-to-high-priority author-layer order: + +```css +@layer mine, domstack.global, domstack.layout, domstack.page; +``` + +The order reflects DOMStack's existing stylesheet scopes: + +- `mine` contains mine.css framework defaults. +- `domstack.global` contains site-wide rules and syntax themes. +- `domstack.layout` contains optional mine.css layout rules and `*.layout.css` rules. +- `domstack.page` contains page-local `style.css` rules. + +Declare the complete order before imports or other layer blocks in the global stylesheet. +Import mine.css normally because its distributed stylesheet already defines the `mine` layer. +Do not write `@import 'mine.css' layer(mine)`. +Optional sidecars and syntax themes are not pre-layered, so assign them to the appropriate DOMStack layer: + +```css +@layer mine, domstack.global, domstack.layout, domstack.page; + +@import 'mine.css'; +@import 'mine.css/dist/layout.css' layer(domstack.layout); +@import 'highlight.js/styles/github.css' layer(domstack.global); +@import url('highlight.js/styles/github-dark-dimmed.css') layer(domstack.global) (prefers-color-scheme: dark); + +@layer domstack.global { + :root { + --brand-color: rebeccapurple; + } +} +``` + +Following this pattern in custom stylesheets is recommended. +Scope layout and page rules in their matching files: + +```css +/* article.layout.css */ +@layer domstack.layout { + .article-shell { + max-inline-size: 72rem; + } +} +``` + +```css +/* style.css */ +@layer domstack.page { + .article-introduction { + font-size: 1.125em; + } +} +``` + +DOMStack loads the global stylesheet first, so its order declaration establishes precedence before layout and page layer blocks are encountered. +Unlayered author rules outrank every layered normal rule, so existing unlayered overrides will still win but bypass the DOMStack scope contract. +Move them into the matching layer when you want predictable global → layout → page precedence. +CSS Modules may remain unlayered when component-local precedence is intentional, or be wrapped in the appropriate scope layer when they participate in this cascade. + +mine.css v11 also intentionally changes typography, forms, tables, media framing, motion, focus treatment, and the optional `.mine-layout` width. +It preserves native table display; wrap wide tables in an accessible, named, keyboard-focusable overflow region rather than restoring `display: block` on the table. +The distributed CSS uses native CSS nesting, and the package requires Node.js 22 or newer and npm 10 or newer for installation. +Visually verify representative pages in light and dark browser modes, narrow and wide viewports, reduced-motion mode, and print preview where relevant. + +--- + +## 7. Migration Checklist - [ ] If you import public types from `@domstack/static`, update those imports to `@domstack/static/types.js`. - [ ] If you rely on BrowserSync-specific dev-server behavior, test watch mode with `@domstack/sync`. @@ -133,3 +223,11 @@ export default async function esbuildSettingsOverride (esbuildSettings) { - [ ] If you want your ejected server-side layout to match the v12 default, migrate its templates to `fragtml` and install `fragtml`. - [ ] If you use `.jsx` or `.tsx` browser clients, add an `esbuild.settings` file that configures your JSX runtime. - [ ] If you use Preact browser clients, keep `preact` in your project dependencies. +- [ ] Read the mine.css v11 migration guide and account for its intentional visual and browser-support changes. +- [ ] Replace `@import 'mine.css/dist/mine.css'` with `@import 'mine.css'` and keep optional sidecars explicit. +- [ ] Remove `toggleTheme()`, theme-switcher imports, persisted theme state, theme controls, and light/dark mode classes. +- [ ] Add `` to custom root layouts and use `prefers-color-scheme` for dark styles. +- [ ] Declare `@layer mine, domstack.global, domstack.layout, domstack.page;` and scope global, layout, and page rules in their matching layers. +- [ ] Load light and dark syntax-highlighting themes with matching `prefers-color-scheme` behavior. +- [ ] Confirm the deployment and install environment uses Node.js 22+ and npm 10+ and that target browsers support native CSS nesting. +- [ ] Visually and interactively check both color schemes, keyboard focus, reduced motion, forms, wide tables, media, print, and responsive layouts. diff --git a/examples/basic/package.json b/examples/basic/package.json index 0c33dec8..016360c3 100644 --- a/examples/basic/package.json +++ b/examples/basic/package.json @@ -21,7 +21,7 @@ "dependencies": { "@domstack/static": "file:../../.", "fragtml": "^0.0.9", - "mine.css": "^9.0.1", + "mine.css": "^11.0.3", "highlight.js": "^11.9.0" } } diff --git a/examples/basic/src/global.client.ts b/examples/basic/src/global.client.ts index b840c4d9..dae17c34 100644 --- a/examples/basic/src/global.client.ts +++ b/examples/basic/src/global.client.ts @@ -1,15 +1,6 @@ -// @ts-expect-error -import { toggleTheme } from 'mine.css' -declare global { - interface Window { - toggleTheme: typeof toggleTheme; - } -} - -window.toggleTheme = toggleTheme console.log('The global client is loaded on every page.') // Try to keep this file as small as possible. -// Use if for things like global theme switchers or analytics scripts +// Use it for things like analytics scripts or other site-wide behavior diff --git a/examples/basic/src/global.css b/examples/basic/src/global.css index 15bc3690..bbe42032 100644 --- a/examples/basic/src/global.css +++ b/examples/basic/src/global.css @@ -1,10 +1,15 @@ -@import 'mine.css/dist/mine.css'; -@import 'mine.css/dist/layout.css'; -@import 'highlight.js/styles/github-dark-dimmed.css'; +@layer mine, domstack.global, domstack.layout, domstack.page; -/* The global.css in the root directory of the site is loaded on every page. */ +@import 'mine.css'; +@import 'mine.css/dist/layout.css' layer(domstack.layout); +@import 'highlight.js/styles/github.css' layer(domstack.global); +@import url('highlight.js/styles/github-dark-dimmed.css') layer(domstack.global) (prefers-color-scheme: dark); -/* You can import styles out of node_modules by referencing their bare-name */ -/* You can import styles from a relative path as well, though usually that is -only used in page scpped style.css files */ -/* See https://github.com/postcss/postcss-import for details */ +@layer domstack.global { + /* The global.css in the root directory of the site is loaded on every page. */ + + /* You can import styles out of node_modules by referencing their bare-name */ + /* You can import styles from a relative path as well, though usually that is + only used in page scpped style.css files */ + /* See https://github.com/postcss/postcss-import for details */ +} diff --git a/examples/basic/src/html-page/style.css b/examples/basic/src/html-page/style.css index 6696adb3..6105abac 100644 --- a/examples/basic/src/html-page/style.css +++ b/examples/basic/src/html-page/style.css @@ -1,5 +1,7 @@ -/* html pages can also have their own style bundles */ +@layer domstack.page { + /* html pages can also have their own style bundles */ -.html-page-class { - background-color: red; + .html-page-class { + background-color: red; + } } diff --git a/examples/basic/src/js-page/loose-assets/style.css b/examples/basic/src/js-page/loose-assets/style.css index df552d4d..c2172497 100644 --- a/examples/basic/src/js-page/loose-assets/style.css +++ b/examples/basic/src/js-page/loose-assets/style.css @@ -1,2 +1 @@ -@import './local-import.css'; - +@import './local-import.css' layer(domstack.page); diff --git a/examples/basic/src/js-page/style.css b/examples/basic/src/js-page/style.css index c1673966..7b11d9a3 100644 --- a/examples/basic/src/js-page/style.css +++ b/examples/basic/src/js-page/style.css @@ -1,74 +1,76 @@ -.js-page-example { - max-width: 800px; - margin: 0 auto; - padding: 1rem; - font-family: system-ui, -apple-system, sans-serif; -} +@layer domstack.page { + .js-page-example { + max-width: 800px; + margin: 0 auto; + padding: 1rem; + font-family: system-ui, -apple-system, sans-serif; + } -.js-page-example h1 { - color: #333; - border-bottom: 2px solid #6200ee; - padding-bottom: 0.5rem; -} + .js-page-example h1 { + color: #333; + border-bottom: 2px solid #6200ee; + padding-bottom: 0.5rem; + } -.js-page-example section { - margin-bottom: 2rem; - padding: 1.5rem; - border-radius: 8px; - background-color: #f9f9f9; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); -} + .js-page-example section { + margin-bottom: 2rem; + padding: 1.5rem; + border-radius: 8px; + background-color: #f9f9f9; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + } -.js-page-example h2 { - color: #6200ee; - margin-top: 0; - margin-bottom: 1rem; -} + .js-page-example h2 { + color: #6200ee; + margin-top: 0; + margin-bottom: 1rem; + } -.js-page-example .code-example { - background-color: #272822; - color: #f8f8f2; - padding: 1rem; - border-radius: 4px; - overflow-x: auto; -} + .js-page-example .code-example { + background-color: #272822; + color: #f8f8f2; + padding: 1rem; + border-radius: 4px; + overflow-x: auto; + } -.js-page-example .code-example pre { - margin: 0; -} + .js-page-example .code-example pre { + margin: 0; + } -.js-page-example code { - background-color: #eee; - padding: 0.2rem 0.4rem; - border-radius: 3px; - font-family: monospace; - font-size: 0.9em; -} + .js-page-example code { + background-color: #eee; + padding: 0.2rem 0.4rem; + border-radius: 3px; + font-family: monospace; + font-size: 0.9em; + } -.js-page-example .code-example code { - background-color: transparent; - padding: 0; -} + .js-page-example .code-example code { + background-color: transparent; + padding: 0; + } -.js-page-example .variable-display { - background-color: #fff; - border: 1px solid #ddd; - padding: 1rem; - border-radius: 4px; - margin-top: 1rem; -} + .js-page-example .variable-display { + background-color: #fff; + border: 1px solid #ddd; + padding: 1rem; + border-radius: 4px; + margin-top: 1rem; + } -.js-page-example .back-link { - display: inline-block; - margin-top: 1rem; - padding: 0.5rem 1rem; - background-color: #6200ee; - color: white; - text-decoration: none; - border-radius: 4px; - transition: background-color 0.2s; -} + .js-page-example .back-link { + display: inline-block; + margin-top: 1rem; + padding: 0.5rem 1rem; + background-color: #6200ee; + color: white; + text-decoration: none; + border-radius: 4px; + transition: background-color 0.2s; + } -.js-page-example .back-link:hover { - background-color: #3700b3; + .js-page-example .back-link:hover { + background-color: #3700b3; + } } diff --git a/examples/basic/src/layouts/root.layout.ts b/examples/basic/src/layouts/root.layout.ts index a2da1624..c7da72fe 100644 --- a/examples/basic/src/layouts/root.layout.ts +++ b/examples/basic/src/layouts/root.layout.ts @@ -34,6 +34,7 @@ const RootLayout: LayoutFunction = async ${siteName}${title ? ` | ${title}` : ''} + ${scripts ? scripts.map(script => html``) : null} diff --git a/examples/basic/src/md-page/style.css b/examples/basic/src/md-page/style.css index 05536d35..a30da591 100644 --- a/examples/basic/src/md-page/style.css +++ b/examples/basic/src/md-page/style.css @@ -1,4 +1,6 @@ -/* This style file only loads in the md-page page */ -.page-scoped-selector { - background-color: blue; +@layer domstack.page { + /* This style file only loads in the md-page page */ + .page-scoped-selector { + background-color: blue; + } } diff --git a/examples/basic/src/style.css b/examples/basic/src/style.css index 702d9eeb..4c3d043f 100644 --- a/examples/basic/src/style.css +++ b/examples/basic/src/style.css @@ -1,5 +1,7 @@ -/* This style.css bundle only loads on the root page. */ +@layer domstack.page { + /* This style.css bundle only loads on the root page. */ -.some-class { - color: blue; + .some-class { + color: blue; + } } diff --git a/examples/blog/package.json b/examples/blog/package.json index 468ec337..c4feb4ad 100644 --- a/examples/blog/package.json +++ b/examples/blog/package.json @@ -20,6 +20,6 @@ "dependencies": { "@domstack/static": "file:../../.", "fragtml": "^0.0.9", - "mine.css": "^10.0.0" + "mine.css": "^11.0.3" } } diff --git a/examples/blog/src/global.client.ts b/examples/blog/src/global.client.ts deleted file mode 100644 index c8f25844..00000000 --- a/examples/blog/src/global.client.ts +++ /dev/null @@ -1,6 +0,0 @@ -// @ts-ignore -import { toggleTheme } from 'mine.css' - -// Expose theme toggle globally for the nav button -// @ts-ignore -window.toggleTheme = toggleTheme diff --git a/examples/blog/src/global.css b/examples/blog/src/global.css index 443bcd17..86dbf6f7 100644 --- a/examples/blog/src/global.css +++ b/examples/blog/src/global.css @@ -1,39 +1,43 @@ -@import 'mine.css/dist/mine.css'; -@import 'mine.css/dist/layout.css'; +@layer mine, domstack.global, domstack.layout, domstack.page; -/* ── Site-wide ── */ +@import 'mine.css'; +@import 'mine.css/dist/layout.css' layer(domstack.layout); -.site-header { - border-bottom: 1px solid var(--border); - padding: 1rem 0; - margin-bottom: 2rem; -} +@layer domstack.global { + /* ── Site-wide ── */ -.site-header-inner { - display: flex; - align-items: baseline; - gap: 1.5rem; -} + .site-header { + border-bottom: 1px solid var(--border); + padding: 1rem 0; + margin-bottom: 2rem; + } -.site-title { - font-size: 1.4rem; - font-weight: 700; - text-decoration: none; - color: inherit; -} + .site-header-inner { + display: flex; + align-items: baseline; + gap: 1.5rem; + } -.site-nav { - display: flex; - gap: 1rem; - list-style: none; - margin: 0; - padding: 0; -} + .site-title { + font-size: 1.4rem; + font-weight: 700; + text-decoration: none; + color: inherit; + } + + .site-nav { + display: flex; + gap: 1rem; + list-style: none; + margin: 0; + padding: 0; + } -.site-footer { - border-top: 1px solid var(--border); - margin-top: 3rem; - padding: 1.5rem 0; - font-size: 0.875rem; - color: var(--text-2); + .site-footer { + border-top: 1px solid var(--border); + margin-top: 3rem; + padding: 1.5rem 0; + font-size: 0.875rem; + color: var(--text-2); + } } diff --git a/examples/blog/src/layouts/post.layout.css b/examples/blog/src/layouts/post.layout.css index 446b2530..a6108603 100644 --- a/examples/blog/src/layouts/post.layout.css +++ b/examples/blog/src/layouts/post.layout.css @@ -1,86 +1,88 @@ -@import './root.layout.css'; +@import './root.layout.css' layer(domstack.layout); -/* ── Post layout ── */ +@layer domstack.layout { + /* ── Post layout ── */ -.post-header { - margin-bottom: 2rem; -} + .post-header { + margin-bottom: 2rem; + } -.post-title { - font-size: 2rem; - line-height: 1.2; - margin-bottom: 0.75rem; -} + .post-title { + font-size: 2rem; + line-height: 1.2; + margin-bottom: 0.75rem; + } -.post-meta { - display: flex; - flex-wrap: wrap; - align-items: baseline; - gap: 0.75rem 1.5rem; - font-size: 0.875rem; - color: var(--text-2); - border-top: 1px solid var(--border); - border-bottom: 1px solid var(--border); - padding: 0.625rem 0; - margin-bottom: 1.5rem; -} + .post-meta { + display: flex; + flex-wrap: wrap; + align-items: baseline; + gap: 0.75rem 1.5rem; + font-size: 0.875rem; + color: var(--text-2); + border-top: 1px solid var(--border); + border-bottom: 1px solid var(--border); + padding: 0.625rem 0; + margin-bottom: 1.5rem; + } -.author-card { - font-style: normal; -} + .author-card { + font-style: normal; + } -.author-name { - font-weight: 600; -} + .author-name { + font-weight: 600; + } -.author-bio { - display: block; - font-size: 0.8rem; - color: var(--text-2); - margin-top: 0.125rem; -} + .author-bio { + display: block; + font-size: 0.8rem; + color: var(--text-2); + margin-top: 0.125rem; + } -.post-date, -.post-updated { - white-space: nowrap; -} + .post-date, + .post-updated { + white-space: nowrap; + } -.post-tags { - display: flex; - flex-wrap: wrap; - gap: 0.375rem; - list-style: none; - margin: 0; - padding: 0; -} + .post-tags { + display: flex; + flex-wrap: wrap; + gap: 0.375rem; + list-style: none; + margin: 0; + padding: 0; + } -.post-tag { - background: var(--surface-2); - border-radius: 0.25rem; - padding: 0.125rem 0.5rem; - font-size: 0.8rem; -} + .post-tag { + background: var(--surface-2); + border-radius: 0.25rem; + padding: 0.125rem 0.5rem; + font-size: 0.8rem; + } -.post-body { - line-height: 1.7; -} + .post-body { + line-height: 1.7; + } -.post-body :is(h2, h3, h4) { - margin-top: 2rem; -} + .post-body :is(h2, h3, h4) { + margin-top: 2rem; + } -.post-body pre { - overflow-x: auto; -} + .post-body pre { + overflow-x: auto; + } -.post-footer { - margin-top: 2.5rem; - padding-top: 1rem; - border-top: 1px solid var(--border); - font-size: 0.875rem; - color: var(--text-2); -} + .post-footer { + margin-top: 2.5rem; + padding-top: 1rem; + border-top: 1px solid var(--border); + font-size: 0.875rem; + color: var(--text-2); + } -.post-footer-note { - margin: 0; + .post-footer-note { + margin: 0; + } } diff --git a/examples/blog/src/layouts/root.layout.css b/examples/blog/src/layouts/root.layout.css index 21595dfc..d48fce96 100644 --- a/examples/blog/src/layouts/root.layout.css +++ b/examples/blog/src/layouts/root.layout.css @@ -1,45 +1,47 @@ -/* Base layout styles shared by all layout variants */ - -/* ── Post index list (used by home + /blog/) ── */ - -.post-list { - list-style: none; - margin: 0; - padding: 0; - display: flex; - flex-direction: column; - gap: 1.5rem; -} - -.post-list-item { - border-bottom: 1px solid var(--border); - padding-bottom: 1.5rem; -} - -.post-list-item:last-child { - border-bottom: none; -} - -.post-list-title { - font-size: 1.25rem; - margin: 0 0 0.25rem; -} - -.post-list-title a { - text-decoration: none; -} - -.post-list-title a:hover { - text-decoration: underline; -} - -.post-list-meta { - font-size: 0.8rem; - color: var(--text-2); - margin: 0 0 0.5rem; -} - -.post-list-description { - margin: 0; - color: var(--text-2); +@layer domstack.layout { + /* Base layout styles shared by all layout variants */ + + /* ── Post index list (used by home + /blog/) ── */ + + .post-list { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 1.5rem; + } + + .post-list-item { + border-bottom: 1px solid var(--border); + padding-bottom: 1.5rem; + } + + .post-list-item:last-child { + border-bottom: none; + } + + .post-list-title { + font-size: 1.25rem; + margin: 0 0 0.25rem; + } + + .post-list-title a { + text-decoration: none; + } + + .post-list-title a:hover { + text-decoration: underline; + } + + .post-list-meta { + font-size: 0.8rem; + color: var(--text-2); + margin: 0 0 0.5rem; + } + + .post-list-description { + margin: 0; + color: var(--text-2); + } } diff --git a/examples/blog/src/layouts/root.layout.ts b/examples/blog/src/layouts/root.layout.ts index 8fa620cb..69c376d2 100644 --- a/examples/blog/src/layouts/root.layout.ts +++ b/examples/blog/src/layouts/root.layout.ts @@ -22,6 +22,7 @@ const rootLayout: LayoutFunction = ({ ${pageTitle} + ${styles?.map(s => html``)} ${scripts?.map(s => html``)} @@ -36,7 +37,7 @@ const rootLayout: LayoutFunction = ({
  • Blog
  • About
  • Feed
  • -
  • + diff --git a/examples/css-modules/package.json b/examples/css-modules/package.json index 7a9b8ebb..a8f6719a 100644 --- a/examples/css-modules/package.json +++ b/examples/css-modules/package.json @@ -14,7 +14,7 @@ "@preact/signals": "^2.0.0", "highlight.js": "^11.9.0", "htm": "^3.1.1", - "mine.css": "^9.0.1", + "mine.css": "^11.0.3", "preact": "^10.24.0", "preact-render-to-string": "^6.5.11", "@domstack/static": "file:../../." diff --git a/examples/css-modules/src/globals/global.client.js b/examples/css-modules/src/globals/global.client.js deleted file mode 100644 index 20121a79..00000000 --- a/examples/css-modules/src/globals/global.client.js +++ /dev/null @@ -1,2 +0,0 @@ -import { toggleTheme } from 'mine.css' -window.toggleTheme = toggleTheme diff --git a/examples/css-modules/src/globals/global.css b/examples/css-modules/src/globals/global.css index 84d5e65b..e1ca9f76 100644 --- a/examples/css-modules/src/globals/global.css +++ b/examples/css-modules/src/globals/global.css @@ -1,3 +1,6 @@ -@import 'mine.css/dist/mine.css'; -@import 'mine.css/dist/layout.css'; -@import 'highlight.js/styles/github-dark-dimmed.css'; +@layer mine, domstack.global, domstack.layout, domstack.page; + +@import 'mine.css'; +@import 'mine.css/dist/layout.css' layer(domstack.layout); +@import 'highlight.js/styles/github.css' layer(domstack.global); +@import url('highlight.js/styles/github-dark-dimmed.css') layer(domstack.global) (prefers-color-scheme: dark); diff --git a/examples/css-modules/src/layouts/root.layout.js b/examples/css-modules/src/layouts/root.layout.js index 4410e8b3..2427da6d 100644 --- a/examples/css-modules/src/layouts/root.layout.js +++ b/examples/css-modules/src/layouts/root.layout.js @@ -34,6 +34,7 @@ export default async function RootLayout ({ ${siteName}${title ? ` | ${title}` : ''} + ${scripts ? scripts.map(script => html``).join('\n ') : ''} diff --git a/examples/tailwind/src/globals/global.css b/examples/tailwind/src/globals/global.css index 3fbea23b..7e8bd119 100644 --- a/examples/tailwind/src/globals/global.css +++ b/examples/tailwind/src/globals/global.css @@ -1,3 +1,4 @@ +@layer mine, domstack.global, domstack.layout, domstack.page; @import "tailwindcss"; @plugin "@tailwindcss/typography"; diff --git a/examples/tailwind/src/layouts/root.layout.js b/examples/tailwind/src/layouts/root.layout.js index 76167838..6785257f 100644 --- a/examples/tailwind/src/layouts/root.layout.js +++ b/examples/tailwind/src/layouts/root.layout.js @@ -35,6 +35,7 @@ export default function defaultRootLayout ({ ${title ? `${title}` : ''}${title && siteName ? ' | ' : ''}${siteName} + ${scripts ? scripts.map(script => html``) : null} diff --git a/examples/worker-example/package.json b/examples/worker-example/package.json index 44d8b27f..4fb57aa9 100644 --- a/examples/worker-example/package.json +++ b/examples/worker-example/package.json @@ -13,7 +13,7 @@ "author": "", "license": "MIT", "dependencies": { - "mine.css": "^9.0.1", + "mine.css": "^11.0.3", "@domstack/static": "file:../../." }, "devDependencies": { diff --git a/examples/worker-example/src/globals/global.css b/examples/worker-example/src/globals/global.css index ddd247b0..298e3fd2 100644 --- a/examples/worker-example/src/globals/global.css +++ b/examples/worker-example/src/globals/global.css @@ -1,68 +1,72 @@ -@import 'mine.css/dist/mine.css'; -@import 'mine.css/dist/layout.css'; +@layer mine, domstack.global, domstack.layout, domstack.page; -/* Global styles for the worker example */ -body { - font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, - Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; - line-height: 1.6; - color: #333; - background-color: #f8f9fa; -} +@import 'mine.css'; +@import 'mine.css/dist/layout.css' layer(domstack.layout); -h1, h2, h3 { - color: #0d6efd; - margin-top: 1rem; - margin-bottom: 1rem; -} +@layer domstack.global { + /* Global styles for the worker example */ + body { + font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, + Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + line-height: 1.6; + color: #333; + background-color: #f8f9fa; + } -button { - background-color: #0d6efd; - color: white; - border: none; - padding: 0.5rem 1rem; - border-radius: 0.25rem; - cursor: pointer; - font-size: 1rem; - transition: background-color 0.2s; -} + h1, h2, h3 { + color: #0d6efd; + margin-top: 1rem; + margin-bottom: 1rem; + } -button:hover { - background-color: #0b5ed7; -} + button { + background-color: #0d6efd; + color: white; + border: none; + padding: 0.5rem 1rem; + border-radius: 0.25rem; + cursor: pointer; + font-size: 1rem; + transition: background-color 0.2s; + } -button:disabled { - background-color: #6c757d; - cursor: not-allowed; -} + button:hover { + background-color: #0b5ed7; + } -.mine-layout { - padding: 2rem; - max-width: 1000px; - margin: 0 auto; -} + button:disabled { + background-color: #6c757d; + cursor: not-allowed; + } -a { - color: #0d6efd; - text-decoration: none; -} + .mine-layout { + padding: 2rem; + max-width: 1000px; + margin: 0 auto; + } -a:hover { - text-decoration: underline; -} + a { + color: #0d6efd; + text-decoration: none; + } -.navigation { - margin-bottom: 2rem; -} + a:hover { + text-decoration: underline; + } -.navigation a { - margin-right: 1rem; -} + .navigation { + margin-bottom: 2rem; + } + + .navigation a { + margin-right: 1rem; + } -.footer { - margin-top: 3rem; - padding-top: 1rem; - border-top: 1px solid #dee2e6; - text-align: center; - color: #6c757d; -} \ No newline at end of file + .footer { + margin-top: 3rem; + padding-top: 1rem; + border-top: 1px solid #dee2e6; + text-align: center; + color: #6c757d; + } +} diff --git a/examples/worker-example/src/layouts/root.layout.js b/examples/worker-example/src/layouts/root.layout.js index afe650e3..575a3430 100644 --- a/examples/worker-example/src/layouts/root.layout.js +++ b/examples/worker-example/src/layouts/root.layout.js @@ -19,6 +19,7 @@ export default function rootLayout ({ + ${title ? `${title} | ${siteName}` : siteName} ${styles.map(style => ``).join('\n ')} diff --git a/examples/worker-example/src/style.css b/examples/worker-example/src/style.css index 7b1af512..64e4dd9c 100644 --- a/examples/worker-example/src/style.css +++ b/examples/worker-example/src/style.css @@ -1,100 +1,102 @@ -/* Styles for the home page */ -h1 { - color: #0d6efd; - border-bottom: 2px solid #0d6efd; - padding-bottom: 0.5rem; - margin-bottom: 1.5rem; -} +@layer domstack.page { + /* Styles for the home page */ + h1 { + color: #0d6efd; + border-bottom: 2px solid #0d6efd; + padding-bottom: 0.5rem; + margin-bottom: 1.5rem; + } -h2 { - margin-top: 2rem; - color: #495057; -} + h2 { + margin-top: 2rem; + color: #495057; + } -ul { - margin-left: 1.5rem; - line-height: 1.6; -} + ul { + margin-left: 1.5rem; + line-height: 1.6; + } -code { - background-color: #f8f9fa; - padding: 0.2rem 0.4rem; - border-radius: 3px; - font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; - border: 1px solid #e9ecef; - color: #dc3545; -} + code { + background-color: #f8f9fa; + padding: 0.2rem 0.4rem; + border-radius: 3px; + font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + border: 1px solid #e9ecef; + color: #dc3545; + } -pre { - background-color: #f8f9fa; - padding: 1rem; - border-radius: 4px; - border: 1px solid #dee2e6; - overflow-x: auto; - margin: 1.5rem 0; -} + pre { + background-color: #f8f9fa; + padding: 1rem; + border-radius: 4px; + border: 1px solid #dee2e6; + overflow-x: auto; + margin: 1.5rem 0; + } -pre code { - background-color: transparent; - padding: 0; - border: none; - color: #212529; -} + pre code { + background-color: transparent; + padding: 0; + border: none; + color: #212529; + } -.features { - margin: 2rem 0; -} + .features { + margin: 2rem 0; + } -.example-link { - display: inline-block; - margin: 1rem 0; - background-color: #0d6efd; - color: white; - padding: 0.75rem 1.5rem; - border-radius: 4px; - font-weight: bold; - text-decoration: none; - transition: background-color 0.2s; -} + .example-link { + display: inline-block; + margin: 1rem 0; + background-color: #0d6efd; + color: white; + padding: 0.75rem 1.5rem; + border-radius: 4px; + font-weight: bold; + text-decoration: none; + transition: background-color 0.2s; + } -.example-link:hover { - background-color: #0b5ed7; - text-decoration: none; -} + .example-link:hover { + background-color: #0b5ed7; + text-decoration: none; + } -.code-block { - position: relative; - margin: 2rem 0; -} + .code-block { + position: relative; + margin: 2rem 0; + } -.code-block::before { - content: attr(data-title); - position: absolute; - top: -12px; - left: 10px; - background-color: #fff; - padding: 0 0.5rem; - font-size: 0.85rem; - color: #6c757d; - border: 1px solid #dee2e6; - border-radius: 3px; -} + .code-block::before { + content: attr(data-title); + position: absolute; + top: -12px; + left: 10px; + background-color: #fff; + padding: 0 0.5rem; + font-size: 0.85rem; + color: #6c757d; + border: 1px solid #dee2e6; + border-radius: 3px; + } -.file-structure { - font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; - font-size: 0.9rem; - line-height: 1.5; - margin: 1.5rem 0; -} + .file-structure { + font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + font-size: 0.9rem; + line-height: 1.5; + margin: 1.5rem 0; + } -.technical-details { - background-color: #f8f9fa; - padding: 1.5rem; - border-radius: 8px; - border: 1px solid #e9ecef; - margin: 2rem 0; -} + .technical-details { + background-color: #f8f9fa; + padding: 1.5rem; + border-radius: 8px; + border: 1px solid #e9ecef; + margin: 2rem 0; + } -.technical-details h2 { - margin-top: 0; -} \ No newline at end of file + .technical-details h2 { + margin-top: 0; + } +} diff --git a/examples/worker-example/src/worker-page/style.css b/examples/worker-example/src/worker-page/style.css index 032ef514..8fd98945 100644 --- a/examples/worker-example/src/worker-page/style.css +++ b/examples/worker-example/src/worker-page/style.css @@ -1,109 +1,111 @@ -/* Worker example page styles */ -.worker-example { - background-color: white; - border-radius: 8px; - box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); - padding: 2rem; - margin-bottom: 2rem; +@layer domstack.page { + /* Worker example page styles */ + .worker-example { + background-color: white; + border-radius: 8px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); + padding: 2rem; + margin-bottom: 2rem; + } + + .worker-example h1 { + color: #0d6efd; + border-bottom: 2px solid #0d6efd; + padding-bottom: 0.5rem; + margin-bottom: 1.5rem; + } + + .worker-example h2 { + color: #495057; + margin-top: 1.5rem; + margin-bottom: 1rem; + } + + .explanation { + margin-bottom: 2rem; + } + + .explanation ul { + margin-left: 1.5rem; + } + + .demo-section { + background-color: #f8f9fa; + border-radius: 8px; + padding: 1.5rem; + margin: 2rem 0; + border: 1px solid #e9ecef; + } + + .demo-container { + display: flex; + flex-direction: column; + gap: 1rem; + } + + .counter-display, .result { + background-color: white; + padding: 1rem; + border-radius: 4px; + border: 1px solid #dee2e6; + position: relative; + } + + .input-group { + display: flex; + align-items: center; + gap: 0.5rem; + flex-wrap: wrap; + } + + .controls { + display: flex; + gap: 0.5rem; + flex-wrap: wrap; + margin-bottom: 0.5rem; + } + + .computation-time { + font-size: 0.9rem; + color: #6c757d; + margin-top: 0.5rem; + } + + input { + padding: 0.5rem; + border: 1px solid #ced4da; + border-radius: 4px; + font-size: 1rem; + width: 80px; + } + + #counter, #fibResult { + font-weight: bold; + color: #0d6efd; + font-size: 1.2rem; + } + + .last-operation { + font-size: 0.85rem; + color: #6c757d; + margin-top: 0.5rem; + font-style: italic; + } + + .code-example { + margin: 2rem 0; + } + + .code-example pre { + background-color: #f8f9fa; + padding: 1rem; + border-radius: 4px; + border: 1px solid #dee2e6; + overflow-x: auto; + margin-bottom: 1.5rem; + } + + .code-example code { + font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + } } - -.worker-example h1 { - color: #0d6efd; - border-bottom: 2px solid #0d6efd; - padding-bottom: 0.5rem; - margin-bottom: 1.5rem; -} - -.worker-example h2 { - color: #495057; - margin-top: 1.5rem; - margin-bottom: 1rem; -} - -.explanation { - margin-bottom: 2rem; -} - -.explanation ul { - margin-left: 1.5rem; -} - -.demo-section { - background-color: #f8f9fa; - border-radius: 8px; - padding: 1.5rem; - margin: 2rem 0; - border: 1px solid #e9ecef; -} - -.demo-container { - display: flex; - flex-direction: column; - gap: 1rem; -} - -.counter-display, .result { - background-color: white; - padding: 1rem; - border-radius: 4px; - border: 1px solid #dee2e6; - position: relative; -} - -.input-group { - display: flex; - align-items: center; - gap: 0.5rem; - flex-wrap: wrap; -} - -.controls { - display: flex; - gap: 0.5rem; - flex-wrap: wrap; - margin-bottom: 0.5rem; -} - -.computation-time { - font-size: 0.9rem; - color: #6c757d; - margin-top: 0.5rem; -} - -input { - padding: 0.5rem; - border: 1px solid #ced4da; - border-radius: 4px; - font-size: 1rem; - width: 80px; -} - -#counter, #fibResult { - font-weight: bold; - color: #0d6efd; - font-size: 1.2rem; -} - -.last-operation { - font-size: 0.85rem; - color: #6c757d; - margin-top: 0.5rem; - font-style: italic; -} - -.code-example { - margin: 2rem 0; -} - -.code-example pre { - background-color: #f8f9fa; - padding: 1rem; - border-radius: 4px; - border: 1px solid #dee2e6; - overflow-x: auto; - margin-bottom: 1.5rem; -} - -.code-example code { - font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; -} \ No newline at end of file diff --git a/global.css b/global.css index 80a69162..ebe93617 100644 --- a/global.css +++ b/global.css @@ -1,10 +1,14 @@ -/* Global styles applied to every page */ +@layer mine, domstack.global, domstack.layout, domstack.page; -@font-face { +@layer domstack.global { + /* Global styles applied to every page */ + + @font-face { font-family: 'DSWeiss-Gotisch'; src: url('./fonts/ds-weiss-gotisch/DSWeiss-Gotisch.ttf') format('truetype'); -} + } -h1, h2, h3, h4, h5, h6 { + h1, h2, h3, h4, h5, h6 { font-family: 'DSWeiss-Gotisch', serif; + } } diff --git a/lib/defaults/default.client.js b/lib/defaults/default.client.js index e2babe95..0b6af0b8 100644 --- a/lib/defaults/default.client.js +++ b/lib/defaults/default.client.js @@ -1,4 +1 @@ -// @ts-expect-error -import { toggleTheme } from 'mine.css' -// @ts-expect-error -window.toggleTheme = toggleTheme +// mine.css follows the browser's color preference and needs no theme JavaScript. diff --git a/lib/defaults/default.root.layout.js b/lib/defaults/default.root.layout.js index 4b53a76c..10646f12 100644 --- a/lib/defaults/default.root.layout.js +++ b/lib/defaults/default.root.layout.js @@ -38,6 +38,7 @@ export default function defaultRootLayout ({ ${title ? `${title}` : ''}${title && siteName ? ' | ' : ''}${siteName} + ${scripts ? scripts.map(script => html``) : null} diff --git a/lib/defaults/default.style.css b/lib/defaults/default.style.css index 84d5e65b..e1ca9f76 100644 --- a/lib/defaults/default.style.css +++ b/lib/defaults/default.style.css @@ -1,3 +1,6 @@ -@import 'mine.css/dist/mine.css'; -@import 'mine.css/dist/layout.css'; -@import 'highlight.js/styles/github-dark-dimmed.css'; +@layer mine, domstack.global, domstack.layout, domstack.page; + +@import 'mine.css'; +@import 'mine.css/dist/layout.css' layer(domstack.layout); +@import 'highlight.js/styles/github.css' layer(domstack.global); +@import url('highlight.js/styles/github-dark-dimmed.css') layer(domstack.global) (prefers-color-scheme: dark); diff --git a/package.json b/package.json index 81275b43..a8c5c8e5 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "write-package": "^7.0.1" }, "devDependencies": { + "@playwright/test": "^1.61.1", "@types/markdown-it": "^14.1.1", "@types/markdown-it-footnote": "^3.0.4", "@types/node": "^26.0.1", @@ -92,6 +93,7 @@ "test:installed-check": "installed-check --ignore-dev", "test:neostandard": "eslint . --ignore-pattern 'test-cases/build-errors/src/**/*.js' --ignore-pattern 'test-cases/page-build-errors/src/**/*.js'", "test:node-test": "node --test --experimental-test-coverage --test-reporter=dot --test-reporter=lcov --test-reporter-destination=stdout --test-reporter-destination=lcov.info", + "test:playwright": "playwright test", "test:tsc": "tsc", "build-examples": "run-p example:*", "clean": "run-p clean:*", diff --git a/playwright.config.js b/playwright.config.js new file mode 100644 index 00000000..1a62dbbb --- /dev/null +++ b/playwright.config.js @@ -0,0 +1,24 @@ +import { defineConfig, devices } from '@playwright/test' + +export default defineConfig({ + testDir: './browser-tests', + timeout: 10_000, + globalTimeout: 120_000, + expect: { + timeout: 5_000 + }, + fullyParallel: false, + workers: 1, + reporter: 'list', + outputDir: 'test-results/playwright', + use: { + trace: 'on-first-retry', + screenshot: 'only-on-failure' + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] } + } + ] +}) diff --git a/style.css b/style.css index 87b60892..fe1ba5fa 100644 --- a/style.css +++ b/style.css @@ -1 +1,3 @@ -/* Styles go here */ +@layer domstack.page { + /* Styles go here */ +} diff --git a/test-cases/build-errors/src/style.css b/test-cases/build-errors/src/style.css index 629fe3a2..9c4e4c24 100644 --- a/test-cases/build-errors/src/style.css +++ b/test-cases/build-errors/src/style.css @@ -1,7 +1,9 @@ -.some-class { - background: blue; +@layer domstack.page { + .some-class { + background: blue; - /* And some garbled CSS */ + /* And some garbled CSS */ - fdskahfjla; jfk jkl;dfjsla;fjdls943rjn + fdskahfjla; jfk jkl;dfjsla;fjdls943rjn + } } diff --git a/test-cases/default-layout/src/global.css b/test-cases/default-layout/src/global.css index c81052b4..0434dc2c 100644 --- a/test-cases/default-layout/src/global.css +++ b/test-cases/default-layout/src/global.css @@ -1,3 +1,7 @@ -.global-class { - color: blue; +@layer mine, domstack.global, domstack.layout, domstack.page; + +@layer domstack.global { + .global-class { + color: blue; + } } diff --git a/test-cases/default-layout/src/style.css b/test-cases/default-layout/src/style.css index c63ffb86..0ae629de 100644 --- a/test-cases/default-layout/src/style.css +++ b/test-cases/default-layout/src/style.css @@ -1,4 +1,6 @@ -/* You can still do page styles and get the default layout. */ -.root-page-style { - background: blue; +@layer domstack.page { + /* You can still do page styles and get the default layout. */ + .root-page-style { + background: blue; + } } diff --git a/test-cases/general-features/src/README.md b/test-cases/general-features/src/README.md index f86c5632..26ba9b3c 100644 --- a/test-cases/general-features/src/README.md +++ b/test-cases/general-features/src/README.md @@ -5,4 +5,6 @@ testVar: frontmatter var This is a README.md in the root of the site. +
    Cascade layer fixture
    + {{{ vars.blogPostsHtml }}} diff --git a/test-cases/general-features/src/blog/2023/a-draft-from-2023/style.css b/test-cases/general-features/src/blog/2023/a-draft-from-2023/style.css index 34c7c797..c5deeb2f 100644 --- a/test-cases/general-features/src/blog/2023/a-draft-from-2023/style.css +++ b/test-cases/general-features/src/blog/2023/a-draft-from-2023/style.css @@ -1,3 +1,5 @@ -.a-blog-post-css { - color: blue; +@layer domstack.page { + .a-blog-post-css { + color: blue; + } } diff --git a/test-cases/general-features/src/blog/2023/the-first-blog-post-from-2023/style.css b/test-cases/general-features/src/blog/2023/the-first-blog-post-from-2023/style.css index 34c7c797..c5deeb2f 100644 --- a/test-cases/general-features/src/blog/2023/the-first-blog-post-from-2023/style.css +++ b/test-cases/general-features/src/blog/2023/the-first-blog-post-from-2023/style.css @@ -1,3 +1,5 @@ -.a-blog-post-css { - color: blue; +@layer domstack.page { + .a-blog-post-css { + color: blue; + } } diff --git a/test-cases/general-features/src/globals/global.client.js b/test-cases/general-features/src/globals/global.client.js index b6455a6f..8edeb4d7 100644 --- a/test-cases/general-features/src/globals/global.client.js +++ b/test-cases/general-features/src/globals/global.client.js @@ -1,8 +1,3 @@ -// @ts-ignore -import { toggleTheme } from 'mine.css' console.log('This runs on every page') export default 'global.client.js' - -// @ts-ignore -window.toggleTheme = toggleTheme diff --git a/test-cases/general-features/src/globals/global.css b/test-cases/general-features/src/globals/global.css index 45e5274e..8ef024c5 100644 --- a/test-cases/general-features/src/globals/global.css +++ b/test-cases/general-features/src/globals/global.css @@ -1,5 +1,10 @@ -@import 'mine.css/dist/mine.css'; -@import 'mine.css/dist/layout.css'; +@layer mine, domstack.global, domstack.layout, domstack.page; -.test-asset-icon { background-image: url('./test-icon.gif'); } -@font-face { font-family: 'TestFont'; src: url('./test-font.woff2'); } +@import 'mine.css'; +@import 'mine.css/dist/layout.css' layer(domstack.layout); + +@layer domstack.global { + .test-asset-icon { background-image: url('./test-icon.gif'); } + @font-face { font-family: 'TestFont'; src: url('./test-font.woff2'); } + .cascade-layer-fixture { --domstack-cascade-layer: global; } +} diff --git a/test-cases/general-features/src/html-page/style.css b/test-cases/general-features/src/html-page/style.css index f62d0e5f..42e3fbd6 100644 --- a/test-cases/general-features/src/html-page/style.css +++ b/test-cases/general-features/src/html-page/style.css @@ -1,3 +1,5 @@ -.htnl-page-style { - background: blue; +@layer domstack.page { + .htnl-page-style { + background: blue; + } } diff --git a/test-cases/general-features/src/js-page/style.css b/test-cases/general-features/src/js-page/style.css index e5c571c5..a50e29e8 100644 --- a/test-cases/general-features/src/js-page/style.css +++ b/test-cases/general-features/src/js-page/style.css @@ -1,3 +1,5 @@ -.js-page-style { - background: purple; +@layer domstack.page { + .js-page-style { + background: purple; + } } diff --git a/test-cases/general-features/src/layouts/blog.layout.css b/test-cases/general-features/src/layouts/blog.layout.css index da49c7ab..4d0df864 100644 --- a/test-cases/general-features/src/layouts/blog.layout.css +++ b/test-cases/general-features/src/layouts/blog.layout.css @@ -1,5 +1,7 @@ -@import "./root.layout.css"; +@import "./root.layout.css" layer(domstack.layout); -.some-blog-class { - color: purple; +@layer domstack.layout { + .some-blog-class { + color: purple; + } } diff --git a/test-cases/general-features/src/layouts/root.layout.css b/test-cases/general-features/src/layouts/root.layout.css index be7e6ff1..ff3881aa 100644 --- a/test-cases/general-features/src/layouts/root.layout.css +++ b/test-cases/general-features/src/layouts/root.layout.css @@ -1,3 +1,6 @@ -.some-root-layout-class { - color: green; +@layer domstack.layout { + .cascade-layer-fixture { --domstack-cascade-layer: layout; } + .some-root-layout-class { + color: green; + } } diff --git a/test-cases/general-features/src/md-page/style.css b/test-cases/general-features/src/md-page/style.css index 4d55f5ee..9e64847a 100644 --- a/test-cases/general-features/src/md-page/style.css +++ b/test-cases/general-features/src/md-page/style.css @@ -1,45 +1,47 @@ -.md-page-style { - background: yellow; -} +@layer domstack.page { + .md-page-style { + background: yellow; + } -/* Style for the custom test-box container */ -.test-box { - border: 2px solid #3498db; - border-radius: 5px; - padding: 15px; - margin: 20px 0; - background-color: #eaf7ff; - position: relative; -} + /* Style for the custom test-box container */ + .test-box { + border: 2px solid #3498db; + border-radius: 5px; + padding: 15px; + margin: 20px 0; + background-color: #eaf7ff; + position: relative; + } -.test-box::before { - content: "Test Box"; - position: absolute; - top: -12px; - left: 10px; - background-color: white; - padding: 0 10px; - color: #3498db; - font-weight: bold; -} + .test-box::before { + content: "Test Box"; + position: absolute; + top: -12px; + left: 10px; + background-color: white; + padding: 0 10px; + color: #3498db; + font-weight: bold; + } -/* Style for the custom test-box container */ -.test-box { - border: 2px solid #3498db; - border-radius: 5px; - padding: 15px; - margin: 20px 0; - background-color: #eaf7ff; - position: relative; -} + /* Style for the custom test-box container */ + .test-box { + border: 2px solid #3498db; + border-radius: 5px; + padding: 15px; + margin: 20px 0; + background-color: #eaf7ff; + position: relative; + } -.test-box::before { - content: "Test Box"; - position: absolute; - top: -12px; - left: 10px; - background-color: white; - padding: 0 10px; - color: #3498db; - font-weight: bold; + .test-box::before { + content: "Test Box"; + position: absolute; + top: -12px; + left: 10px; + background-color: white; + padding: 0 10px; + color: #3498db; + font-weight: bold; + } } diff --git a/test-cases/general-features/src/style.css b/test-cases/general-features/src/style.css index e27b8523..a9f2311d 100644 --- a/test-cases/general-features/src/style.css +++ b/test-cases/general-features/src/style.css @@ -1,3 +1,6 @@ -.root-page-style { - background: blue; +@layer domstack.page { + .cascade-layer-fixture { --domstack-cascade-layer: page; } + .root-page-style { + background: blue; + } } diff --git a/test-cases/general-features/src/worker-page/style.css b/test-cases/general-features/src/worker-page/style.css index 0ffc6688..dfe4838b 100644 --- a/test-cases/general-features/src/worker-page/style.css +++ b/test-cases/general-features/src/worker-page/style.css @@ -1,45 +1,47 @@ -.worker-test { - max-width: 800px; - margin: 0 auto; - padding: 1rem; -} +@layer domstack.page { + .worker-test { + max-width: 800px; + margin: 0 auto; + padding: 1rem; + } -.worker-component { - margin-top: 2rem; - padding: 1.5rem; - border: 1px solid #ccc; - border-radius: 8px; - background-color: #f9f9f9; -} + .worker-component { + margin-top: 2rem; + padding: 1.5rem; + border: 1px solid #ccc; + border-radius: 8px; + background-color: #f9f9f9; + } -.counter-display { - margin-bottom: 1rem; - font-size: 1.2rem; -} + .counter-display { + margin-bottom: 1rem; + font-size: 1.2rem; + } -#counter { - font-weight: bold; - color: #0066cc; -} + #counter { + font-weight: bold; + color: #0066cc; + } -.controls { - display: flex; - gap: 0.5rem; -} + .controls { + display: flex; + gap: 0.5rem; + } -button { - padding: 0.5rem 1rem; - background-color: #0066cc; - color: white; - border: none; - border-radius: 4px; - cursor: pointer; -} + button { + padding: 0.5rem 1rem; + background-color: #0066cc; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + } -button:hover { - background-color: #0055aa; -} + button:hover { + background-color: #0055aa; + } -button:active { - background-color: #004488; -} \ No newline at end of file + button:active { + background-color: #004488; + } +} diff --git a/test-cases/nested-dest/md-page/style.css b/test-cases/nested-dest/md-page/style.css index 87b51d5f..f923ec3e 100644 --- a/test-cases/nested-dest/md-page/style.css +++ b/test-cases/nested-dest/md-page/style.css @@ -1,3 +1,5 @@ -.md-page-style { - background: yellow; +@layer domstack.page { + .md-page-style { + background: yellow; + } } diff --git a/test-cases/nested-dest/style.css b/test-cases/nested-dest/style.css index c63ffb86..0ae629de 100644 --- a/test-cases/nested-dest/style.css +++ b/test-cases/nested-dest/style.css @@ -1,4 +1,6 @@ -/* You can still do page styles and get the default layout. */ -.root-page-style { - background: blue; +@layer domstack.page { + /* You can still do page styles and get the default layout. */ + .root-page-style { + background: blue; + } } diff --git a/test-cases/page-build-errors/src/style.css b/test-cases/page-build-errors/src/style.css index 460b8541..0651c6bc 100644 --- a/test-cases/page-build-errors/src/style.css +++ b/test-cases/page-build-errors/src/style.css @@ -1,3 +1,5 @@ -.some-class { - background: blue; +@layer domstack.page { + .some-class { + background: blue; + } } From 41401336155efa9c88d5ec95d12e5a0f18bcb4f8 Mon Sep 17 00:00:00 2001 From: Bret Comnes Date: Fri, 17 Jul 2026 16:25:31 -0700 Subject: [PATCH 3/4] Install Playwright Chromium in CI --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2f9305e9..51671fa2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -22,6 +22,7 @@ jobs: node-version: ${{ matrix.node-version }} check-latest: true - run: npm i + - run: npx playwright install --with-deps chromium - name: Run tests run: | node_major=$(node -p "process.versions.node.split('.')[0]") From 78c58024d39ab82242cfce32f8efe70077214a53 Mon Sep 17 00:00:00 2001 From: Bret Comnes Date: Fri, 17 Jul 2026 16:29:35 -0700 Subject: [PATCH 4/4] Update mine.css to 11.0.6 --- examples/basic/package.json | 2 +- examples/blog/package.json | 2 +- examples/css-modules/package.json | 2 +- examples/nested-dest/package.json | 2 +- examples/preact-isomorphic/package.json | 2 +- examples/react/package.json | 2 +- examples/type-stripping/package.json | 2 +- examples/uhtml-isomorphic/package.json | 2 +- examples/worker-example/package.json | 2 +- package.json | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/examples/basic/package.json b/examples/basic/package.json index 016360c3..1061cbc3 100644 --- a/examples/basic/package.json +++ b/examples/basic/package.json @@ -21,7 +21,7 @@ "dependencies": { "@domstack/static": "file:../../.", "fragtml": "^0.0.9", - "mine.css": "^11.0.3", + "mine.css": "^11.0.6", "highlight.js": "^11.9.0" } } diff --git a/examples/blog/package.json b/examples/blog/package.json index c4feb4ad..039bf160 100644 --- a/examples/blog/package.json +++ b/examples/blog/package.json @@ -20,6 +20,6 @@ "dependencies": { "@domstack/static": "file:../../.", "fragtml": "^0.0.9", - "mine.css": "^11.0.3" + "mine.css": "^11.0.6" } } diff --git a/examples/css-modules/package.json b/examples/css-modules/package.json index a8f6719a..c2a5e24b 100644 --- a/examples/css-modules/package.json +++ b/examples/css-modules/package.json @@ -14,7 +14,7 @@ "@preact/signals": "^2.0.0", "highlight.js": "^11.9.0", "htm": "^3.1.1", - "mine.css": "^11.0.3", + "mine.css": "^11.0.6", "preact": "^10.24.0", "preact-render-to-string": "^6.5.11", "@domstack/static": "file:../../." diff --git a/examples/nested-dest/package.json b/examples/nested-dest/package.json index 2e67aef6..f2a819e4 100644 --- a/examples/nested-dest/package.json +++ b/examples/nested-dest/package.json @@ -15,7 +15,7 @@ "license": "MIT", "dependencies": { "@domstack/static": "file:../../.", - "mine.css": "^11.0.3" + "mine.css": "^11.0.6" }, "devDependencies": { "npm-run-all2": "^6.0.0" diff --git a/examples/preact-isomorphic/package.json b/examples/preact-isomorphic/package.json index 93e3de76..0ef756aa 100644 --- a/examples/preact-isomorphic/package.json +++ b/examples/preact-isomorphic/package.json @@ -14,7 +14,7 @@ "@preact/signals": "^2.0.0", "highlight.js": "^11.9.0", "htm": "^3.1.1", - "mine.css": "^11.0.3", + "mine.css": "^11.0.6", "preact": "^10.24.0", "preact-render-to-string": "^6.5.11", "@domstack/static": "file:../../." diff --git a/examples/react/package.json b/examples/react/package.json index 3e69a526..70cd5a86 100644 --- a/examples/react/package.json +++ b/examples/react/package.json @@ -14,7 +14,7 @@ "dependencies": { "@domstack/static": "file:../../.", "htm": "^3.1.1", - "mine.css": "^11.0.3", + "mine.css": "^11.0.6", "react": "^19.1.1" }, "devDependencies": { diff --git a/examples/type-stripping/package.json b/examples/type-stripping/package.json index 93e3de76..0ef756aa 100644 --- a/examples/type-stripping/package.json +++ b/examples/type-stripping/package.json @@ -14,7 +14,7 @@ "@preact/signals": "^2.0.0", "highlight.js": "^11.9.0", "htm": "^3.1.1", - "mine.css": "^11.0.3", + "mine.css": "^11.0.6", "preact": "^10.24.0", "preact-render-to-string": "^6.5.11", "@domstack/static": "file:../../." diff --git a/examples/uhtml-isomorphic/package.json b/examples/uhtml-isomorphic/package.json index 548b5e9a..aab92988 100644 --- a/examples/uhtml-isomorphic/package.json +++ b/examples/uhtml-isomorphic/package.json @@ -12,7 +12,7 @@ "license": "MIT", "dependencies": { "highlight.js": "^11.9.0", - "mine.css": "^11.0.3", + "mine.css": "^11.0.6", "uhtml-isomorphic": "^2.1.0", "@domstack/static": "../../." }, diff --git a/examples/worker-example/package.json b/examples/worker-example/package.json index 4fb57aa9..b6f6ad27 100644 --- a/examples/worker-example/package.json +++ b/examples/worker-example/package.json @@ -13,7 +13,7 @@ "author": "", "license": "MIT", "dependencies": { - "mine.css": "^11.0.3", + "mine.css": "^11.0.6", "@domstack/static": "file:../../." }, "devDependencies": { diff --git a/package.json b/package.json index a8c5c8e5..fbcc254c 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "markdown-it-sup": "^2.0.0", "markdown-it-table-of-contents": "^1.1.0", "markdown-it-task-lists": "^2.1.1", - "mine.css": "^11.0.3", + "mine.css": "^11.0.6", "p-map": "^7.0.2", "package-directory": "^8.1.0", "package-json": "^10.0.0",