diff --git a/__tests__/build-css.test.js b/__tests__/build-css.test.js
new file mode 100644
index 0000000..4a5ccb2
--- /dev/null
+++ b/__tests__/build-css.test.js
@@ -0,0 +1,147 @@
+/**
+ * Tests for CSS build script (scripts/build-css.js)
+ * Tests minification logic and build output.
+ */
+
+const fs = require('fs-extra');
+const path = require('path');
+const { execSync } = require('child_process');
+
+const rootDir = path.resolve(__dirname, '..');
+const stylesDir = path.join(rootDir, 'src', 'styles');
+const outputFile = path.join(rootDir, 'style.css');
+
+describe('CSS Build Script', () => {
+ let originalContent;
+
+ beforeAll(async () => {
+ // Save original style.css for restoration
+ if (await fs.pathExists(outputFile)) {
+ originalContent = await fs.readFile(outputFile, 'utf-8');
+ }
+ });
+
+ afterAll(async () => {
+ // Restore original style.css
+ if (originalContent) {
+ await fs.writeFile(outputFile, originalContent, 'utf-8');
+ }
+ });
+
+ test('styles directory contains CSS files', async () => {
+ const files = await fs.readdir(stylesDir);
+ const cssFiles = files.filter(f => f.endsWith('.css'));
+ expect(cssFiles.length).toBeGreaterThan(0);
+ expect(cssFiles).toContain('_variables.css');
+ expect(cssFiles).toContain('_08_themes.css');
+ });
+
+ test('build:css produces readable output with comments', () => {
+ const result = execSync('node scripts/build-css.js', { cwd: rootDir, encoding: 'utf-8' });
+
+ expect(result).toContain('Building CSS from src/styles/');
+
+ const content = fs.readFileSync(outputFile, 'utf-8');
+ // Readable build should contain section comments
+ expect(content).toContain('/* ============================================ */');
+ expect(content).toContain('/* _variables.css */');
+ });
+
+ test('build:css --minify produces minified output', () => {
+ const result = execSync('node scripts/build-css.js --minify', { cwd: rootDir, encoding: 'utf-8' });
+
+ expect(result).toContain('minified');
+ expect(result).toContain('Minification:');
+
+ const content = fs.readFileSync(outputFile, 'utf-8');
+ // Minified build should NOT contain section comments
+ expect(content).not.toContain('/* ============================================ */');
+ // Should not have multiple consecutive spaces (except in strings)
+ expect(content).not.toMatch(/[^\S\r\n]{2,}/);
+ // Should not have newlines between rules
+ const lines = content.split('\n');
+ expect(lines.length).toBeLessThan(1000); // Original is ~12000 lines
+ });
+
+ test('minified output is smaller than readable output', () => {
+ // Build readable
+ execSync('node scripts/build-css.js', { cwd: rootDir, encoding: 'utf-8' });
+ const readableStats = fs.statSync(outputFile);
+ const readableSize = readableStats.size;
+
+ // Build minified
+ execSync('node scripts/build-css.js --minify', { cwd: rootDir, encoding: 'utf-8' });
+ const minifiedStats = fs.statSync(outputFile);
+ const minifiedSize = minifiedStats.size;
+
+ expect(minifiedSize).toBeLessThan(readableSize);
+ // Expect at least 30% reduction (comments + whitespace)
+ const reduction = ((readableSize - minifiedSize) / readableSize) * 100;
+ expect(reduction).toBeGreaterThan(30);
+
+ // Restore readable version for other tests
+ execSync('node scripts/build-css.js', { cwd: rootDir, encoding: 'utf-8' });
+ });
+
+ test('minified output preserves CSS custom properties', () => {
+ execSync('node scripts/build-css.js --minify', { cwd: rootDir, encoding: 'utf-8' });
+
+ const content = fs.readFileSync(outputFile, 'utf-8');
+ // CSS variables should be preserved
+ expect(content).toContain('--rpg-bg');
+ expect(content).toContain('--rpg-accent');
+ expect(content).toContain('--rpg-text');
+ expect(content).toContain('--rpg-highlight');
+ expect(content).toContain('--rpg-border');
+ expect(content).toContain('--rpg-shadow');
+ });
+
+ test('minified output preserves data URIs', () => {
+ execSync('node scripts/build-css.js --minify', { cwd: rootDir, encoding: 'utf-8' });
+
+ const content = fs.readFileSync(outputFile, 'utf-8');
+ // Fantasy theme has SVG data URI ā should be preserved
+ expect(content).toContain('data:image/svg+xml');
+ });
+
+ test('minified output preserves data-theme selectors', () => {
+ execSync('node scripts/build-css.js --minify', { cwd: rootDir, encoding: 'utf-8' });
+
+ const content = fs.readFileSync(outputFile, 'utf-8');
+ expect(content).toContain('[data-theme="sci-fi"]');
+ expect(content).toContain('[data-theme="fantasy"]');
+ expect(content).toContain('[data-theme="cyberpunk"]');
+ });
+
+ test('themes.css uses grouped [data-theme] selectors (no duplication)', async () => {
+ const themesContent = await fs.readFile(path.join(stylesDir, '_08_themes.css'), 'utf-8');
+
+ // Should use [data-theme] grouped selectors instead of repeating all 3 themes
+ expect(themesContent).toContain('.rpg-panel[data-theme] .rpg-tabs-nav');
+ expect(themesContent).toContain('.rpg-panel[data-theme] .rpg-tab-btn');
+
+ // Should NOT have the old pattern of listing all 3 themes for every element
+ const oldPattern = /\.rpg-panel\[data-theme="sci-fi"\] \.rpg-tabs-nav,\s*\.rpg-panel\[data-theme="fantasy"\] \.rpg-tabs-nav,\s*\.rpg-panel\[data-theme="cyberpunk"\] \.rpg-tabs-nav/;
+ expect(themesContent).not.toMatch(oldPattern);
+ });
+
+ test('themes.css defines extended custom properties per theme', async () => {
+ const themesContent = await fs.readFile(path.join(stylesDir, '_08_themes.css'), 'utf-8');
+
+ // Extended properties for content box
+ expect(themesContent).toContain('--rpg-content-bg');
+ expect(themesContent).toContain('--rpg-content-box-shadow');
+ expect(themesContent).toContain('--rpg-content-border');
+
+ // Extended properties for headers
+ expect(themesContent).toContain('--rpg-header-text-shadow');
+ expect(themesContent).toContain('--rpg-header-font-family');
+
+ // Extended properties for dividers
+ expect(themesContent).toContain('--rpg-divider-bg');
+ expect(themesContent).toContain('--rpg-divider-content');
+
+ // Extended properties for thoughts
+ expect(themesContent).toContain('--rpg-thoughts-bg');
+ });
+});
diff --git a/package-lock.json b/package-lock.json
index c9dbf66..e722a2e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,6 +13,7 @@
"@babel/preset-env": "^8.0.2",
"@jest/globals": "^30.4.1",
"chokidar": "^5.0.0",
+ "execa": "^9.6.1",
"fs-extra": "^11.3.3",
"glob": "^13.0.6",
"jest": "^30.4.2"
@@ -3662,6 +3663,13 @@
"url": "https://opencollective.com/pkgr"
}
},
+ "node_modules/@sec-ant/readable-stream": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz",
+ "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@sinclair/typebox": {
"version": "0.34.52",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz",
@@ -3669,6 +3677,19 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@sindresorhus/merge-streams": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz",
+ "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/@sinonjs/commons": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz",
@@ -4984,24 +5005,27 @@
}
},
"node_modules/execa": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
- "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz",
+ "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "cross-spawn": "^7.0.3",
- "get-stream": "^6.0.0",
- "human-signals": "^2.1.0",
- "is-stream": "^2.0.0",
- "merge-stream": "^2.0.0",
- "npm-run-path": "^4.0.1",
- "onetime": "^5.1.2",
- "signal-exit": "^3.0.3",
- "strip-final-newline": "^2.0.0"
+ "@sindresorhus/merge-streams": "^4.0.0",
+ "cross-spawn": "^7.0.6",
+ "figures": "^6.1.0",
+ "get-stream": "^9.0.0",
+ "human-signals": "^8.0.1",
+ "is-plain-obj": "^4.1.0",
+ "is-stream": "^4.0.1",
+ "npm-run-path": "^6.0.0",
+ "pretty-ms": "^9.2.0",
+ "signal-exit": "^4.1.0",
+ "strip-final-newline": "^4.0.0",
+ "yoctocolors": "^2.1.1"
},
"engines": {
- "node": ">=10"
+ "node": "^18.19.0 || >=20.5.0"
},
"funding": {
"url": "https://github.com/sindresorhus/execa?sponsor=1"
@@ -5052,6 +5076,22 @@
"bser": "2.1.1"
}
},
+ "node_modules/figures": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz",
+ "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-unicode-supported": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
@@ -5083,19 +5123,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/foreground-child/node_modules/signal-exit": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
- "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
"node_modules/fs-extra": {
"version": "11.3.6",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.6.tgz",
@@ -5164,13 +5191,17 @@
}
},
"node_modules/get-stream": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
- "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz",
+ "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==",
"dev": true,
"license": "MIT",
+ "dependencies": {
+ "@sec-ant/readable-stream": "^0.4.1",
+ "is-stream": "^4.0.1"
+ },
"engines": {
- "node": ">=10"
+ "node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -5219,13 +5250,13 @@
"license": "MIT"
},
"node_modules/human-signals": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
- "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz",
+ "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==",
"dev": true,
"license": "Apache-2.0",
"engines": {
- "node": ">=10.17.0"
+ "node": ">=18.18.0"
}
},
"node_modules/import-local": {
@@ -5315,14 +5346,40 @@
"node": ">=6"
}
},
- "node_modules/is-stream": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
- "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+ "node_modules/is-plain-obj": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
+ "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=8"
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-stream": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz",
+ "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-unicode-supported": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz",
+ "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -5652,6 +5709,96 @@
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
+ "node_modules/jest-changed-files/node_modules/execa": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
+ "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^6.0.0",
+ "human-signals": "^2.1.0",
+ "is-stream": "^2.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^4.0.1",
+ "onetime": "^5.1.2",
+ "signal-exit": "^3.0.3",
+ "strip-final-newline": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
+ "node_modules/jest-changed-files/node_modules/get-stream": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/jest-changed-files/node_modules/human-signals": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
+ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=10.17.0"
+ }
+ },
+ "node_modules/jest-changed-files/node_modules/is-stream": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/jest-changed-files/node_modules/npm-run-path": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
+ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/jest-changed-files/node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/jest-changed-files/node_modules/strip-final-newline": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
+ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/jest-circus": {
"version": "30.4.2",
"resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.4.2.tgz",
@@ -6987,16 +7134,33 @@
}
},
"node_modules/npm-run-path": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
- "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz",
+ "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "path-key": "^3.0.0"
+ "path-key": "^4.0.0",
+ "unicorn-magic": "^0.3.0"
},
"engines": {
- "node": ">=8"
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/npm-run-path/node_modules/path-key": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
+ "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/obug": {
@@ -7120,6 +7284,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/parse-ms": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz",
+ "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -7239,6 +7416,22 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
+ "node_modules/pretty-ms": {
+ "version": "9.3.0",
+ "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz",
+ "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parse-ms": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/pure-rand": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz",
@@ -7414,11 +7607,17 @@
}
},
"node_modules/signal-exit": {
- "version": "3.0.7",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
- "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"dev": true,
- "license": "ISC"
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
},
"node_modules/slash": {
"version": "3.0.0",
@@ -7554,13 +7753,16 @@
}
},
"node_modules/strip-final-newline": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
- "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz",
+ "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=6"
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/strip-json-comments": {
@@ -7762,6 +7964,19 @@
"node": ">=4"
}
},
+ "node_modules/unicorn-magic": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz",
+ "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/universalify": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
@@ -7940,19 +8155,6 @@
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
- "node_modules/write-file-atomic/node_modules/signal-exit": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
- "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
@@ -8011,6 +8213,19 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
+ },
+ "node_modules/yoctocolors": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz",
+ "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
}
}
}
diff --git a/package.json b/package.json
index d5f029b..1080f99 100644
--- a/package.json
+++ b/package.json
@@ -6,6 +6,7 @@
"type": "module",
"scripts": {
"build:css": "node scripts/build-css.js",
+ "build:css:min": "node scripts/build-css.js --minify",
"test": "jest",
"validate_locale": "node src/i18n/validator.js --watch",
"validate_locale_once": "node src/i18n/validator.js"
@@ -18,6 +19,7 @@
"@babel/preset-env": "^8.0.2",
"@jest/globals": "^30.4.1",
"chokidar": "^5.0.0",
+ "execa": "^9.6.1",
"fs-extra": "^11.3.3",
"glob": "^13.0.6",
"jest": "^30.4.2"
diff --git a/scripts/build-css.js b/scripts/build-css.js
index 7795949..1c99b3d 100644
--- a/scripts/build-css.js
+++ b/scripts/build-css.js
@@ -3,7 +3,9 @@
* CSS Build Script
* Concatenates modular CSS files from src/styles/ into style.css
*
- * Usage: node scripts/build-css.js
+ * Usage:
+ * node scripts/build-css.js # Development build (readable)
+ * node scripts/build-css.js --minify # Production build (minified)
*
* File order matters! Files are concatenated in alphabetical order.
* Prefix files with numbers to control order: _01_variables.css, _02_panel.css, etc.
@@ -19,8 +21,62 @@ const rootDir = path.resolve(__dirname, '..');
const stylesDir = path.join(rootDir, 'src', 'styles');
const outputFile = path.join(rootDir, 'style.css');
+const shouldMinify = process.argv.includes('--minify');
+
+/**
+ * Minify CSS by removing comments, unnecessary whitespace, and redundant characters.
+ * Safe for CSS custom properties, strings, and data URIs.
+ */
+function minifyCSS(css) {
+ let result = css;
+
+ // Remove CSS comments (/* ... */) ā but not inside strings
+ result = result.replace(/\/\*[\s\S]*?\*\//g, '');
+
+ // Remove lines that are only whitespace after comment removal
+ result = result.replace(/^\s*[\r\n]/g, '\n');
+
+ // Collapse multiple newlines into one
+ result = result.replace(/\n{2,}/g, '\n');
+
+ // Remove spaces around structural characters outside of strings
+ result = result.replace(/(\{|\}|;|:|,)/g, '$1');
+
+ // Remove space before {
+ result = result.replace(/\s+\{/g, '{');
+
+ // Remove space after {
+ result = result.replace(/\{\s+/g, '{');
+
+ // Remove space before }
+ result = result.replace(/\s+\}/g, '}');
+
+ // Remove space after }
+ result = result.replace(/\}\s+/g, '}');
+
+ // Remove space around :
+ result = result.replace(/\s*:\s*/g, ':');
+
+ // Remove space around ;
+ result = result.replace(/;\s*/g, ';');
+
+ // Remove space around ,
+ result = result.replace(/,\s*/g, ',');
+
+ // Remove trailing semicolon before }
+ result = result.replace(/;\}/g, '}');
+
+ // Collapse multiple spaces into one (preserves spaces inside strings roughly)
+ result = result.replace(/[ \t]+/g, ' ');
+
+ // Remove leading/trailing whitespace
+ result = result.trim();
+
+ return result;
+}
+
async function buildCSS() {
- console.log('Building CSS from src/styles/...');
+ console.log(`Building CSS from src/styles/...${shouldMinify ? ' (minified)' : ' (readable)'}`);
// Read all CSS files from src/styles/
const files = await fs.readdir(stylesDir);
@@ -42,15 +98,30 @@ async function buildCSS() {
for (const file of cssFiles) {
const content = await fs.readFile(file, 'utf-8');
const basename = path.basename(file);
- contents.push(`/* ============================================ */`);
- contents.push(`/* ${basename} */`);
- contents.push(`/* ============================================ */`);
- contents.push('');
- contents.push(content);
- contents.push('');
+
+ if (shouldMinify) {
+ // In minified mode, skip comment separators
+ contents.push(content);
+ } else {
+ contents.push(`/* ============================================ */`);
+ contents.push(`/* ${basename} */`);
+ contents.push(`/* ============================================ */`);
+ contents.push('');
+ contents.push(content);
+ contents.push('');
+ }
}
- const combined = contents.join('\n');
+ let combined = contents.join('\n');
+
+ // Apply minification if requested
+ if (shouldMinify) {
+ const beforeSize = combined.length;
+ combined = minifyCSS(combined);
+ const afterSize = combined.length;
+ const reduction = (((beforeSize - afterSize) / beforeSize) * 100).toFixed(1);
+ console.log(`\nšļø Minification: ${beforeSize.toLocaleString()} ā ${afterSize.toLocaleString()} bytes (${reduction}% reduction)`);
+ }
// Write to style.css
await fs.writeFile(outputFile, combined, 'utf-8');
diff --git a/src/styles/_08_themes.css b/src/styles/_08_themes.css
index 2a13fad..b68f4c9 100644
--- a/src/styles/_08_themes.css
+++ b/src/styles/_08_themes.css
@@ -10,6 +10,12 @@
--rpg-highlight: #ff006e;
--rpg-border: #8b00ff;
--rpg-shadow: rgba(139, 0, 255, 0.5);
+ --rpg-content-bg: linear-gradient(135deg, #1a1f3a 0%, #0a0e27 100%);
+ --rpg-content-box-shadow: 0 0 40px rgba(139, 0, 255, 0.4), inset 0 0 30px rgba(0, 0, 0, 0.5);
+ --rpg-content-border: 2px solid #8b00ff;
+ --rpg-header-text-shadow: 0 0 20px #ff006e, 0 0 40px #8b00ff;
+ --rpg-divider-bg: linear-gradient(to right, transparent, #8b00ff, #ff006e, #8b00ff, transparent);
+ --rpg-thoughts-bg: linear-gradient(135deg, rgba(10, 14, 39, 0.9) 0%, rgba(26, 31, 58, 0.8) 50%, rgba(10, 14, 39, 0.9) 100%);
}
/* Apply sci-fi theme to thought panel */
@@ -25,32 +31,6 @@
--rpg-shadow: rgba(139, 0, 255, 0.5);
}
-.rpg-panel[data-theme="sci-fi"] .rpg-content-box {
- background: linear-gradient(135deg, #1a1f3a 0%, #0a0e27 100%);
- box-shadow: 0 0 40px rgba(139, 0, 255, 0.4), inset 0 0 30px rgba(0, 0, 0, 0.5);
- border: 2px solid #8b00ff;
-}
-
-.rpg-panel[data-theme="sci-fi"] .rpg-panel-header h3,
-.rpg-panel[data-theme="sci-fi"] .rpg-stats-title,
-.rpg-panel[data-theme="sci-fi"] .rpg-info-header,
-.rpg-panel[data-theme="sci-fi"] .rpg-thoughts-header {
- text-shadow: 0 0 20px #ff006e, 0 0 40px #8b00ff;
-}
-
-.rpg-panel[data-theme="sci-fi"] .rpg-divider {
- background: linear-gradient(to right, transparent, #8b00ff, #ff006e, #8b00ff, transparent);
-}
-
-.rpg-panel[data-theme="sci-fi"] .rpg-thoughts-content::before {
- background: linear-gradient(
- 135deg,
- rgba(10, 14, 39, 0.9) 0%,
- rgba(26, 31, 58, 0.8) 50%,
- rgba(10, 14, 39, 0.9) 100%
- );
-}
-
/* Fantasy / Rustic Parchment Theme */
.rpg-panel[data-theme="fantasy"] {
--rpg-bg: #2b1810;
@@ -59,6 +39,16 @@
--rpg-highlight: #d4af37;
--rpg-border: #8b6914;
--rpg-shadow: rgba(0, 0, 0, 0.7);
+ --rpg-bg-image: linear-gradient(rgba(43, 24, 16, 0.9), rgba(43, 24, 16, 0.9)), url('data:image/svg+xml;utf8,');
+ --rpg-content-bg: linear-gradient(135deg, #3d2414 0%, #2b1810 100%);
+ --rpg-content-box-shadow: 0 8px 32px rgba(0, 0, 0, 0.8), inset 0 0 40px rgba(139, 105, 20, 0.2);
+ --rpg-content-border: 3px solid #8b6914;
+ --rpg-content-border-style: ridge;
+ --rpg-header-font-family: 'Georgia', serif;
+ --rpg-header-text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
+ --rpg-divider-content: 'ā¦';
+ --rpg-divider-font-size: clamp(1rem, 1.5vw, 1.5rem);
+ --rpg-thoughts-bg: linear-gradient(135deg, rgba(43, 24, 16, 0.92) 0%, rgba(61, 36, 20, 0.88) 50%, rgba(43, 24, 16, 0.92) 100%);
}
/* Apply fantasy theme to thought panel */
@@ -74,41 +64,6 @@
--rpg-shadow: rgba(0, 0, 0, 0.7);
}
-.rpg-panel[data-theme="fantasy"] {
- background-image:
- linear-gradient(rgba(43, 24, 16, 0.9), rgba(43, 24, 16, 0.9)),
- url('data:image/svg+xml;utf8,');
-}
-
-.rpg-panel[data-theme="fantasy"] .rpg-content-box {
- background: linear-gradient(135deg, #3d2414 0%, #2b1810 100%);
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.8), inset 0 0 40px rgba(139, 105, 20, 0.2);
- border: 3px solid #8b6914;
- border-style: ridge;
-}
-
-.rpg-panel[data-theme="fantasy"] .rpg-panel-header h3,
-.rpg-panel[data-theme="fantasy"] .rpg-stats-title,
-.rpg-panel[data-theme="fantasy"] .rpg-info-header,
-.rpg-panel[data-theme="fantasy"] .rpg-thoughts-header {
- font-family: 'Georgia', serif;
- text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
-}
-
-.rpg-panel[data-theme="fantasy"] .rpg-divider::after {
- content: 'ā¦';
- font-size: clamp(1rem, 1.5vw, 1.5rem);
-}
-
-.rpg-panel[data-theme="fantasy"] .rpg-thoughts-content::before {
- background: linear-gradient(
- 135deg,
- rgba(43, 24, 16, 0.92) 0%,
- rgba(61, 36, 20, 0.88) 50%,
- rgba(43, 24, 16, 0.92) 100%
- );
-}
-
/* Cyberpunk / Neon Grid Theme */
.rpg-panel[data-theme="cyberpunk"] {
--rpg-bg: #000000;
@@ -117,6 +72,17 @@
--rpg-highlight: #ff2a6d;
--rpg-border: #05d9e8;
--rpg-shadow: rgba(5, 217, 232, 0.5);
+ --rpg-bg-image: linear-gradient(rgba(0, 0, 0, 0.95), rgba(0, 0, 0, 0.95)), repeating-linear-gradient(0deg, rgba(5, 217, 232, 0.1) 0px, transparent 1px, transparent 2px, rgba(5, 217, 232, 0.1) 3px), repeating-linear-gradient(90deg, rgba(5, 217, 232, 0.1) 0px, transparent 1px, transparent 2px, rgba(5, 217, 232, 0.1) 3px);
+ --rpg-bg-size: 100% 100%, 30px 30px, 30px 30px;
+ --rpg-content-bg: linear-gradient(135deg, rgba(13, 13, 13, 0.9) 0%, rgba(0, 0, 0, 0.9) 100%);
+ --rpg-content-box-shadow: 0 0 40px rgba(255, 42, 109, 0.4), inset 0 0 30px rgba(5, 217, 232, 0.2);
+ --rpg-content-border: 2px solid #05d9e8;
+ --rpg-header-font-family: 'Courier New', monospace;
+ --rpg-header-letter-spacing: 0.125em;
+ --rpg-header-text-shadow: 0 0 10px #ff2a6d, 0 0 20px #05d9e8, 0 0 30px #ff2a6d;
+ --rpg-divider-bg: linear-gradient(to right, transparent, #05d9e8, #ff2a6d, #05d9e8, transparent);
+ --rpg-divider-height: 0.188rem;
+ --rpg-thoughts-bg: linear-gradient(135deg, rgba(0, 0, 0, 0.92) 0%, rgba(13, 13, 13, 0.85) 50%, rgba(0, 0, 0, 0.92) 100%);
}
/* Apply cyberpunk theme to thought panel */
@@ -132,248 +98,179 @@
--rpg-shadow: rgba(5, 217, 232, 0.5);
}
-.rpg-panel[data-theme="cyberpunk"] {
- background: linear-gradient(rgba(0, 0, 0, 0.95), rgba(0, 0, 0, 0.95)),
- repeating-linear-gradient(0deg, rgba(5, 217, 232, 0.1) 0px, transparent 1px, transparent 2px, rgba(5, 217, 232, 0.1) 3px),
- repeating-linear-gradient(90deg, rgba(5, 217, 232, 0.1) 0px, transparent 1px, transparent 2px, rgba(5, 217, 232, 0.1) 3px);
- background-size: 100% 100%, 30px 30px, 30px 30px;
-}
-
-.rpg-panel[data-theme="cyberpunk"] .rpg-content-box {
- background: linear-gradient(135deg, rgba(13, 13, 13, 0.9) 0%, rgba(0, 0, 0, 0.9) 100%);
- box-shadow: 0 0 40px rgba(255, 42, 109, 0.4), inset 0 0 30px rgba(5, 217, 232, 0.2);
- border: 2px solid #05d9e8;
-}
-
-.rpg-panel[data-theme="cyberpunk"] .rpg-panel-header h3,
-.rpg-panel[data-theme="cyberpunk"] .rpg-stats-title,
-.rpg-panel[data-theme="cyberpunk"] .rpg-info-header,
-.rpg-panel[data-theme="cyberpunk"] .rpg-thoughts-header {
- text-shadow: 0 0 10px #ff2a6d, 0 0 20px #05d9e8, 0 0 30px #ff2a6d;
- font-family: 'Courier New', monospace;
- letter-spacing: 0.125em;
-}
-
-.rpg-panel[data-theme="cyberpunk"] .rpg-divider {
- background: linear-gradient(to right, transparent, #05d9e8, #ff2a6d, #05d9e8, transparent);
- height: 0.188rem;
-}
-
-.rpg-panel[data-theme="cyberpunk"] .rpg-thoughts-content::before {
- background: linear-gradient(
- 135deg,
- rgba(0, 0, 0, 0.92) 0%,
- rgba(13, 13, 13, 0.85) 50%,
- rgba(0, 0, 0, 0.92) 100%
- );
-}
-
/* ============================================
THEME SUPPORT FOR ALL PANEL ELEMENTS
+ Uses CSS custom properties defined above ā no selector duplication needed.
+ Any element inside .rpg-panel inherits the theme variables automatically.
============================================ */
-/* Apply theme colors to tabs navigation */
-.rpg-panel[data-theme="sci-fi"] .rpg-tabs-nav,
-.rpg-panel[data-theme="fantasy"] .rpg-tabs-nav,
-.rpg-panel[data-theme="cyberpunk"] .rpg-tabs-nav {
+/* Apply theme background image if defined */
+.rpg-panel[data-theme] {
+ background-image: var(--rpg-bg-image, none);
+ background-size: var(--rpg-bg-size, auto);
+}
+
+/* Content box theming via custom properties */
+.rpg-panel[data-theme] .rpg-content-box {
+ background: var(--rpg-content-bg);
+ box-shadow: var(--rpg-content-box-shadow);
+ border: var(--rpg-content-border);
+ border-style: var(--rpg-content-border-style, solid);
+}
+
+/* Header theming */
+.rpg-panel[data-theme] .rpg-panel-header h3,
+.rpg-panel[data-theme] .rpg-stats-title,
+.rpg-panel[data-theme] .rpg-info-header,
+.rpg-panel[data-theme] .rpg-thoughts-header {
+ font-family: var(--rpg-header-font-family, inherit);
+ text-shadow: var(--rpg-header-text-shadow, none);
+ letter-spacing: var(--rpg-header-letter-spacing, normal);
+}
+
+/* Divider theming */
+.rpg-panel[data-theme] .rpg-divider {
+ background: var(--rpg-divider-bg);
+ height: var(--rpg-divider-height, auto);
+}
+
+.rpg-panel[data-theme] .rpg-divider::after {
+ content: var(--rpg-divider-content, none);
+ font-size: var(--rpg-divider-font-size, inherit);
+}
+
+/* Thoughts content theming */
+.rpg-panel[data-theme] .rpg-thoughts-content::before {
+ background: var(--rpg-thoughts-bg);
+}
+
+/* Tabs navigation theming */
+.rpg-panel[data-theme] .rpg-tabs-nav {
border-bottom-color: var(--rpg-border);
}
-.rpg-panel[data-theme="sci-fi"] .rpg-tab-btn,
-.rpg-panel[data-theme="fantasy"] .rpg-tab-btn,
-.rpg-panel[data-theme="cyberpunk"] .rpg-tab-btn {
+.rpg-panel[data-theme] .rpg-tab-btn {
background: var(--rpg-accent);
border-color: var(--rpg-border);
color: var(--rpg-text);
}
-.rpg-panel[data-theme="sci-fi"] .rpg-tab-btn:hover,
-.rpg-panel[data-theme="fantasy"] .rpg-tab-btn:hover,
-.rpg-panel[data-theme="cyberpunk"] .rpg-tab-btn:hover {
+.rpg-panel[data-theme] .rpg-tab-btn:hover {
background: var(--rpg-highlight);
border-color: var(--rpg-highlight);
}
-.rpg-panel[data-theme="sci-fi"] .rpg-tab-btn.active,
-.rpg-panel[data-theme="fantasy"] .rpg-tab-btn.active,
-.rpg-panel[data-theme="cyberpunk"] .rpg-tab-btn.active {
+.rpg-panel[data-theme] .rpg-tab-btn.active {
background: var(--rpg-highlight);
border-color: var(--rpg-highlight);
color: var(--rpg-bg);
}
-/* Apply theme colors to inventory subtabs */
-.rpg-panel[data-theme="sci-fi"] .rpg-inventory-subtabs,
-.rpg-panel[data-theme="fantasy"] .rpg-inventory-subtabs,
-.rpg-panel[data-theme="cyberpunk"] .rpg-inventory-subtabs {
+/* Inventory subtabs theming */
+.rpg-panel[data-theme] .rpg-inventory-subtabs {
border-bottom-color: var(--rpg-border);
}
-.rpg-panel[data-theme="sci-fi"] .rpg-inventory-subtab,
-.rpg-panel[data-theme="fantasy"] .rpg-inventory-subtab,
-.rpg-panel[data-theme="cyberpunk"] .rpg-inventory-subtab {
+.rpg-panel[data-theme] .rpg-inventory-subtab {
border-color: var(--rpg-border);
color: var(--rpg-text);
}
-.rpg-panel[data-theme="sci-fi"] .rpg-inventory-subtab:hover,
-.rpg-panel[data-theme="fantasy"] .rpg-inventory-subtab:hover,
-.rpg-panel[data-theme="cyberpunk"] .rpg-inventory-subtab:hover {
+.rpg-panel[data-theme] .rpg-inventory-subtab:hover {
border-color: var(--rpg-highlight);
color: var(--rpg-highlight);
}
-.rpg-panel[data-theme="sci-fi"] .rpg-inventory-subtab.active,
-.rpg-panel[data-theme="fantasy"] .rpg-inventory-subtab.active,
-.rpg-panel[data-theme="cyberpunk"] .rpg-inventory-subtab.active {
+.rpg-panel[data-theme] .rpg-inventory-subtab.active {
border-color: var(--rpg-highlight);
color: var(--rpg-highlight);
}
-/* Apply theme colors to quests subtabs */
-.rpg-panel[data-theme="sci-fi"] .rpg-quests-subtabs,
-.rpg-panel[data-theme="fantasy"] .rpg-quests-subtabs,
-.rpg-panel[data-theme="cyberpunk"] .rpg-quests-subtabs {
+/* Quests subtabs theming */
+.rpg-panel[data-theme] .rpg-quests-subtabs {
border-bottom-color: var(--rpg-border);
}
-.rpg-panel[data-theme="sci-fi"] .rpg-quests-subtab,
-.rpg-panel[data-theme="fantasy"] .rpg-quests-subtab,
-.rpg-panel[data-theme="cyberpunk"] .rpg-quests-subtab {
+.rpg-panel[data-theme] .rpg-quests-subtab {
border-color: var(--rpg-border);
color: var(--rpg-text);
}
-.rpg-panel[data-theme="sci-fi"] .rpg-quests-subtab:hover,
-.rpg-panel[data-theme="fantasy"] .rpg-quests-subtab:hover,
-.rpg-panel[data-theme="cyberpunk"] .rpg-quests-subtab:hover {
+.rpg-panel[data-theme] .rpg-quests-subtab:hover {
border-color: var(--rpg-highlight);
color: var(--rpg-highlight);
}
-.rpg-panel[data-theme="sci-fi"] .rpg-quests-subtab.active,
-.rpg-panel[data-theme="fantasy"] .rpg-quests-subtab.active,
-.rpg-panel[data-theme="cyberpunk"] .rpg-quests-subtab.active {
+.rpg-panel[data-theme] .rpg-quests-subtab.active {
border-color: var(--rpg-highlight);
color: var(--rpg-highlight);
}
-/* Apply theme colors to storage locations */
-.rpg-panel[data-theme="sci-fi"] .rpg-storage-location,
-.rpg-panel[data-theme="fantasy"] .rpg-storage-location,
-.rpg-panel[data-theme="cyberpunk"] .rpg-storage-location {
+/* Storage locations theming */
+.rpg-panel[data-theme] .rpg-storage-location {
border-color: var(--rpg-border);
}
-.rpg-panel[data-theme="sci-fi"] .rpg-storage-header,
-.rpg-panel[data-theme="fantasy"] .rpg-storage-header,
-.rpg-panel[data-theme="cyberpunk"] .rpg-storage-header {
+.rpg-panel[data-theme] .rpg-storage-header {
background: var(--rpg-highlight);
}
-.rpg-panel[data-theme="sci-fi"] .rpg-storage-content,
-.rpg-panel[data-theme="fantasy"] .rpg-storage-content,
-.rpg-panel[data-theme="cyberpunk"] .rpg-storage-content {
+.rpg-panel[data-theme] .rpg-storage-content {
background: var(--rpg-accent);
}
-/* Apply theme colors to buttons */
-.rpg-panel[data-theme="sci-fi"] .rpg-inventory-edit-btn,
-.rpg-panel[data-theme="sci-fi"] .rpg-inventory-add-btn,
-.rpg-panel[data-theme="sci-fi"] .rpg-inventory-remove-btn,
-.rpg-panel[data-theme="sci-fi"] .rpg-quest-edit,
-.rpg-panel[data-theme="sci-fi"] .rpg-quest-remove,
-.rpg-panel[data-theme="sci-fi"] .rpg-add-quest-btn,
-.rpg-panel[data-theme="fantasy"] .rpg-inventory-edit-btn,
-.rpg-panel[data-theme="fantasy"] .rpg-inventory-add-btn,
-.rpg-panel[data-theme="fantasy"] .rpg-inventory-remove-btn,
-.rpg-panel[data-theme="fantasy"] .rpg-quest-edit,
-.rpg-panel[data-theme="fantasy"] .rpg-quest-remove,
-.rpg-panel[data-theme="fantasy"] .rpg-add-quest-btn,
-.rpg-panel[data-theme="cyberpunk"] .rpg-inventory-edit-btn,
-.rpg-panel[data-theme="cyberpunk"] .rpg-inventory-add-btn,
-.rpg-panel[data-theme="cyberpunk"] .rpg-inventory-remove-btn,
-.rpg-panel[data-theme="cyberpunk"] .rpg-quest-edit,
-.rpg-panel[data-theme="cyberpunk"] .rpg-quest-remove,
-.rpg-panel[data-theme="cyberpunk"] .rpg-add-quest-btn {
+/* Button theming */
+.rpg-panel[data-theme] .rpg-inventory-edit-btn,
+.rpg-panel[data-theme] .rpg-inventory-add-btn,
+.rpg-panel[data-theme] .rpg-inventory-remove-btn,
+.rpg-panel[data-theme] .rpg-quest-edit,
+.rpg-panel[data-theme] .rpg-quest-remove,
+.rpg-panel[data-theme] .rpg-add-quest-btn {
background: var(--rpg-accent);
border-color: var(--rpg-border);
color: var(--rpg-text);
}
-.rpg-panel[data-theme="sci-fi"] .rpg-inventory-add-btn,
-.rpg-panel[data-theme="sci-fi"] .rpg-add-quest-btn,
-.rpg-panel[data-theme="fantasy"] .rpg-inventory-add-btn,
-.rpg-panel[data-theme="fantasy"] .rpg-add-quest-btn,
-.rpg-panel[data-theme="cyberpunk"] .rpg-inventory-add-btn,
-.rpg-panel[data-theme="cyberpunk"] .rpg-add-quest-btn {
+.rpg-panel[data-theme] .rpg-inventory-add-btn,
+.rpg-panel[data-theme] .rpg-add-quest-btn {
background: transparent;
border-color: var(--rpg-highlight);
color: var(--rpg-highlight);
}
-/* Apply theme colors to inventory/quest items */
-.rpg-panel[data-theme="sci-fi"] .rpg-inventory-text,
-.rpg-panel[data-theme="sci-fi"] .rpg-quest-item,
-.rpg-panel[data-theme="sci-fi"] .rpg-main-quest-display,
-.rpg-panel[data-theme="fantasy"] .rpg-inventory-text,
-.rpg-panel[data-theme="fantasy"] .rpg-quest-item,
-.rpg-panel[data-theme="fantasy"] .rpg-main-quest-display,
-.rpg-panel[data-theme="cyberpunk"] .rpg-inventory-text,
-.rpg-panel[data-theme="cyberpunk"] .rpg-quest-item,
-.rpg-panel[data-theme="cyberpunk"] .rpg-main-quest-display {
+/* Inventory/quest items theming */
+.rpg-panel[data-theme] .rpg-inventory-text,
+.rpg-panel[data-theme] .rpg-quest-item,
+.rpg-panel[data-theme] .rpg-main-quest-display {
background: var(--rpg-accent);
border-color: var(--rpg-border);
color: var(--rpg-text);
}
-.rpg-panel[data-theme="sci-fi"] .rpg-quest-item:hover,
-.rpg-panel[data-theme="fantasy"] .rpg-quest-item:hover,
-.rpg-panel[data-theme="cyberpunk"] .rpg-quest-item:hover {
+.rpg-panel[data-theme] .rpg-quest-item:hover {
border-color: var(--rpg-highlight);
}
-/* Apply theme colors to hints and empty states */
-.rpg-panel[data-theme="sci-fi"] .rpg-inventory-hint,
-.rpg-panel[data-theme="sci-fi"] .rpg-quest-hint,
-.rpg-panel[data-theme="sci-fi"] .rpg-inventory-empty,
-.rpg-panel[data-theme="sci-fi"] .rpg-quest-empty,
-.rpg-panel[data-theme="fantasy"] .rpg-inventory-hint,
-.rpg-panel[data-theme="fantasy"] .rpg-quest-hint,
-.rpg-panel[data-theme="fantasy"] .rpg-inventory-empty,
-.rpg-panel[data-theme="fantasy"] .rpg-quest-empty,
-.rpg-panel[data-theme="cyberpunk"] .rpg-inventory-hint,
-.rpg-panel[data-theme="cyberpunk"] .rpg-quest-hint,
-.rpg-panel[data-theme="cyberpunk"] .rpg-inventory-empty,
-.rpg-panel[data-theme="cyberpunk"] .rpg-quest-empty {
+/* Hints and empty states theming */
+.rpg-panel[data-theme] .rpg-inventory-hint,
+.rpg-panel[data-theme] .rpg-quest-hint,
+.rpg-panel[data-theme] .rpg-inventory-empty,
+.rpg-panel[data-theme] .rpg-quest-empty {
border-color: var(--rpg-highlight);
color: var(--rpg-text);
}
-/* Apply theme colors to input fields */
-.rpg-panel[data-theme="sci-fi"] .rpg-inline-input,
-.rpg-panel[data-theme="sci-fi"] .rpg-quest-edit-form input,
-.rpg-panel[data-theme="sci-fi"] .rpg-quest-edit-form textarea,
-.rpg-panel[data-theme="fantasy"] .rpg-inline-input,
-.rpg-panel[data-theme="fantasy"] .rpg-quest-edit-form input,
-.rpg-panel[data-theme="fantasy"] .rpg-quest-edit-form textarea,
-.rpg-panel[data-theme="cyberpunk"] .rpg-inline-input,
-.rpg-panel[data-theme="cyberpunk"] .rpg-quest-edit-form input,
-.rpg-panel[data-theme="cyberpunk"] .rpg-quest-edit-form textarea {
+/* Input fields theming */
+.rpg-panel[data-theme] .rpg-inline-input,
+.rpg-panel[data-theme] .rpg-quest-edit-form input,
+.rpg-panel[data-theme] .rpg-quest-edit-form textarea {
background: var(--rpg-bg);
border-color: var(--rpg-border);
color: var(--rpg-text);
}
-.rpg-panel[data-theme="sci-fi"] .rpg-inline-input:focus,
-.rpg-panel[data-theme="sci-fi"] .rpg-quest-edit-form input:focus,
-.rpg-panel[data-theme="sci-fi"] .rpg-quest-edit-form textarea:focus,
-.rpg-panel[data-theme="fantasy"] .rpg-inline-input:focus,
-.rpg-panel[data-theme="fantasy"] .rpg-quest-edit-form input:focus,
-.rpg-panel[data-theme="fantasy"] .rpg-quest-edit-form textarea:focus,
-.rpg-panel[data-theme="cyberpunk"] .rpg-inline-input:focus,
-.rpg-panel[data-theme="cyberpunk"] .rpg-quest-edit-form input:focus,
-.rpg-panel[data-theme="cyberpunk"] .rpg-quest-edit-form textarea:focus {
+.rpg-panel[data-theme] .rpg-inline-input:focus,
+.rpg-panel[data-theme] .rpg-quest-edit-form input:focus,
+.rpg-panel[data-theme] .rpg-quest-edit-form textarea:focus {
border-color: var(--rpg-highlight);
}
-
diff --git a/style.css b/style.css
index 8bad2af..3b28f4b 100644
--- a/style.css
+++ b/style.css
@@ -3292,6 +3292,12 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
--rpg-highlight: #ff006e;
--rpg-border: #8b00ff;
--rpg-shadow: rgba(139, 0, 255, 0.5);
+ --rpg-content-bg: linear-gradient(135deg, #1a1f3a 0%, #0a0e27 100%);
+ --rpg-content-box-shadow: 0 0 40px rgba(139, 0, 255, 0.4), inset 0 0 30px rgba(0, 0, 0, 0.5);
+ --rpg-content-border: 2px solid #8b00ff;
+ --rpg-header-text-shadow: 0 0 20px #ff006e, 0 0 40px #8b00ff;
+ --rpg-divider-bg: linear-gradient(to right, transparent, #8b00ff, #ff006e, #8b00ff, transparent);
+ --rpg-thoughts-bg: linear-gradient(135deg, rgba(10, 14, 39, 0.9) 0%, rgba(26, 31, 58, 0.8) 50%, rgba(10, 14, 39, 0.9) 100%);
}
/* Apply sci-fi theme to thought panel */
@@ -3307,32 +3313,6 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
--rpg-shadow: rgba(139, 0, 255, 0.5);
}
-.rpg-panel[data-theme="sci-fi"] .rpg-content-box {
- background: linear-gradient(135deg, #1a1f3a 0%, #0a0e27 100%);
- box-shadow: 0 0 40px rgba(139, 0, 255, 0.4), inset 0 0 30px rgba(0, 0, 0, 0.5);
- border: 2px solid #8b00ff;
-}
-
-.rpg-panel[data-theme="sci-fi"] .rpg-panel-header h3,
-.rpg-panel[data-theme="sci-fi"] .rpg-stats-title,
-.rpg-panel[data-theme="sci-fi"] .rpg-info-header,
-.rpg-panel[data-theme="sci-fi"] .rpg-thoughts-header {
- text-shadow: 0 0 20px #ff006e, 0 0 40px #8b00ff;
-}
-
-.rpg-panel[data-theme="sci-fi"] .rpg-divider {
- background: linear-gradient(to right, transparent, #8b00ff, #ff006e, #8b00ff, transparent);
-}
-
-.rpg-panel[data-theme="sci-fi"] .rpg-thoughts-content::before {
- background: linear-gradient(
- 135deg,
- rgba(10, 14, 39, 0.9) 0%,
- rgba(26, 31, 58, 0.8) 50%,
- rgba(10, 14, 39, 0.9) 100%
- );
-}
-
/* Fantasy / Rustic Parchment Theme */
.rpg-panel[data-theme="fantasy"] {
--rpg-bg: #2b1810;
@@ -3341,6 +3321,16 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
--rpg-highlight: #d4af37;
--rpg-border: #8b6914;
--rpg-shadow: rgba(0, 0, 0, 0.7);
+ --rpg-bg-image: linear-gradient(rgba(43, 24, 16, 0.9), rgba(43, 24, 16, 0.9)), url('data:image/svg+xml;utf8,');
+ --rpg-content-bg: linear-gradient(135deg, #3d2414 0%, #2b1810 100%);
+ --rpg-content-box-shadow: 0 8px 32px rgba(0, 0, 0, 0.8), inset 0 0 40px rgba(139, 105, 20, 0.2);
+ --rpg-content-border: 3px solid #8b6914;
+ --rpg-content-border-style: ridge;
+ --rpg-header-font-family: 'Georgia', serif;
+ --rpg-header-text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
+ --rpg-divider-content: 'ā¦';
+ --rpg-divider-font-size: clamp(1rem, 1.5vw, 1.5rem);
+ --rpg-thoughts-bg: linear-gradient(135deg, rgba(43, 24, 16, 0.92) 0%, rgba(61, 36, 20, 0.88) 50%, rgba(43, 24, 16, 0.92) 100%);
}
/* Apply fantasy theme to thought panel */
@@ -3356,41 +3346,6 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
--rpg-shadow: rgba(0, 0, 0, 0.7);
}
-.rpg-panel[data-theme="fantasy"] {
- background-image:
- linear-gradient(rgba(43, 24, 16, 0.9), rgba(43, 24, 16, 0.9)),
- url('data:image/svg+xml;utf8,');
-}
-
-.rpg-panel[data-theme="fantasy"] .rpg-content-box {
- background: linear-gradient(135deg, #3d2414 0%, #2b1810 100%);
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.8), inset 0 0 40px rgba(139, 105, 20, 0.2);
- border: 3px solid #8b6914;
- border-style: ridge;
-}
-
-.rpg-panel[data-theme="fantasy"] .rpg-panel-header h3,
-.rpg-panel[data-theme="fantasy"] .rpg-stats-title,
-.rpg-panel[data-theme="fantasy"] .rpg-info-header,
-.rpg-panel[data-theme="fantasy"] .rpg-thoughts-header {
- font-family: 'Georgia', serif;
- text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
-}
-
-.rpg-panel[data-theme="fantasy"] .rpg-divider::after {
- content: 'ā¦';
- font-size: clamp(1rem, 1.5vw, 1.5rem);
-}
-
-.rpg-panel[data-theme="fantasy"] .rpg-thoughts-content::before {
- background: linear-gradient(
- 135deg,
- rgba(43, 24, 16, 0.92) 0%,
- rgba(61, 36, 20, 0.88) 50%,
- rgba(43, 24, 16, 0.92) 100%
- );
-}
-
/* Cyberpunk / Neon Grid Theme */
.rpg-panel[data-theme="cyberpunk"] {
--rpg-bg: #000000;
@@ -3399,6 +3354,17 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
--rpg-highlight: #ff2a6d;
--rpg-border: #05d9e8;
--rpg-shadow: rgba(5, 217, 232, 0.5);
+ --rpg-bg-image: linear-gradient(rgba(0, 0, 0, 0.95), rgba(0, 0, 0, 0.95)), repeating-linear-gradient(0deg, rgba(5, 217, 232, 0.1) 0px, transparent 1px, transparent 2px, rgba(5, 217, 232, 0.1) 3px), repeating-linear-gradient(90deg, rgba(5, 217, 232, 0.1) 0px, transparent 1px, transparent 2px, rgba(5, 217, 232, 0.1) 3px);
+ --rpg-bg-size: 100% 100%, 30px 30px, 30px 30px;
+ --rpg-content-bg: linear-gradient(135deg, rgba(13, 13, 13, 0.9) 0%, rgba(0, 0, 0, 0.9) 100%);
+ --rpg-content-box-shadow: 0 0 40px rgba(255, 42, 109, 0.4), inset 0 0 30px rgba(5, 217, 232, 0.2);
+ --rpg-content-border: 2px solid #05d9e8;
+ --rpg-header-font-family: 'Courier New', monospace;
+ --rpg-header-letter-spacing: 0.125em;
+ --rpg-header-text-shadow: 0 0 10px #ff2a6d, 0 0 20px #05d9e8, 0 0 30px #ff2a6d;
+ --rpg-divider-bg: linear-gradient(to right, transparent, #05d9e8, #ff2a6d, #05d9e8, transparent);
+ --rpg-divider-height: 0.188rem;
+ --rpg-thoughts-bg: linear-gradient(135deg, rgba(0, 0, 0, 0.92) 0%, rgba(13, 13, 13, 0.85) 50%, rgba(0, 0, 0, 0.92) 100%);
}
/* Apply cyberpunk theme to thought panel */
@@ -3414,253 +3380,184 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
--rpg-shadow: rgba(5, 217, 232, 0.5);
}
-.rpg-panel[data-theme="cyberpunk"] {
- background: linear-gradient(rgba(0, 0, 0, 0.95), rgba(0, 0, 0, 0.95)),
- repeating-linear-gradient(0deg, rgba(5, 217, 232, 0.1) 0px, transparent 1px, transparent 2px, rgba(5, 217, 232, 0.1) 3px),
- repeating-linear-gradient(90deg, rgba(5, 217, 232, 0.1) 0px, transparent 1px, transparent 2px, rgba(5, 217, 232, 0.1) 3px);
- background-size: 100% 100%, 30px 30px, 30px 30px;
-}
-
-.rpg-panel[data-theme="cyberpunk"] .rpg-content-box {
- background: linear-gradient(135deg, rgba(13, 13, 13, 0.9) 0%, rgba(0, 0, 0, 0.9) 100%);
- box-shadow: 0 0 40px rgba(255, 42, 109, 0.4), inset 0 0 30px rgba(5, 217, 232, 0.2);
- border: 2px solid #05d9e8;
-}
-
-.rpg-panel[data-theme="cyberpunk"] .rpg-panel-header h3,
-.rpg-panel[data-theme="cyberpunk"] .rpg-stats-title,
-.rpg-panel[data-theme="cyberpunk"] .rpg-info-header,
-.rpg-panel[data-theme="cyberpunk"] .rpg-thoughts-header {
- text-shadow: 0 0 10px #ff2a6d, 0 0 20px #05d9e8, 0 0 30px #ff2a6d;
- font-family: 'Courier New', monospace;
- letter-spacing: 0.125em;
-}
-
-.rpg-panel[data-theme="cyberpunk"] .rpg-divider {
- background: linear-gradient(to right, transparent, #05d9e8, #ff2a6d, #05d9e8, transparent);
- height: 0.188rem;
-}
-
-.rpg-panel[data-theme="cyberpunk"] .rpg-thoughts-content::before {
- background: linear-gradient(
- 135deg,
- rgba(0, 0, 0, 0.92) 0%,
- rgba(13, 13, 13, 0.85) 50%,
- rgba(0, 0, 0, 0.92) 100%
- );
-}
-
/* ============================================
THEME SUPPORT FOR ALL PANEL ELEMENTS
+ Uses CSS custom properties defined above ā no selector duplication needed.
+ Any element inside .rpg-panel inherits the theme variables automatically.
============================================ */
-/* Apply theme colors to tabs navigation */
-.rpg-panel[data-theme="sci-fi"] .rpg-tabs-nav,
-.rpg-panel[data-theme="fantasy"] .rpg-tabs-nav,
-.rpg-panel[data-theme="cyberpunk"] .rpg-tabs-nav {
+/* Apply theme background image if defined */
+.rpg-panel[data-theme] {
+ background-image: var(--rpg-bg-image, none);
+ background-size: var(--rpg-bg-size, auto);
+}
+
+/* Content box theming via custom properties */
+.rpg-panel[data-theme] .rpg-content-box {
+ background: var(--rpg-content-bg);
+ box-shadow: var(--rpg-content-box-shadow);
+ border: var(--rpg-content-border);
+ border-style: var(--rpg-content-border-style, solid);
+}
+
+/* Header theming */
+.rpg-panel[data-theme] .rpg-panel-header h3,
+.rpg-panel[data-theme] .rpg-stats-title,
+.rpg-panel[data-theme] .rpg-info-header,
+.rpg-panel[data-theme] .rpg-thoughts-header {
+ font-family: var(--rpg-header-font-family, inherit);
+ text-shadow: var(--rpg-header-text-shadow, none);
+ letter-spacing: var(--rpg-header-letter-spacing, normal);
+}
+
+/* Divider theming */
+.rpg-panel[data-theme] .rpg-divider {
+ background: var(--rpg-divider-bg);
+ height: var(--rpg-divider-height, auto);
+}
+
+.rpg-panel[data-theme] .rpg-divider::after {
+ content: var(--rpg-divider-content, none);
+ font-size: var(--rpg-divider-font-size, inherit);
+}
+
+/* Thoughts content theming */
+.rpg-panel[data-theme] .rpg-thoughts-content::before {
+ background: var(--rpg-thoughts-bg);
+}
+
+/* Tabs navigation theming */
+.rpg-panel[data-theme] .rpg-tabs-nav {
border-bottom-color: var(--rpg-border);
}
-.rpg-panel[data-theme="sci-fi"] .rpg-tab-btn,
-.rpg-panel[data-theme="fantasy"] .rpg-tab-btn,
-.rpg-panel[data-theme="cyberpunk"] .rpg-tab-btn {
+.rpg-panel[data-theme] .rpg-tab-btn {
background: var(--rpg-accent);
border-color: var(--rpg-border);
color: var(--rpg-text);
}
-.rpg-panel[data-theme="sci-fi"] .rpg-tab-btn:hover,
-.rpg-panel[data-theme="fantasy"] .rpg-tab-btn:hover,
-.rpg-panel[data-theme="cyberpunk"] .rpg-tab-btn:hover {
+.rpg-panel[data-theme] .rpg-tab-btn:hover {
background: var(--rpg-highlight);
border-color: var(--rpg-highlight);
}
-.rpg-panel[data-theme="sci-fi"] .rpg-tab-btn.active,
-.rpg-panel[data-theme="fantasy"] .rpg-tab-btn.active,
-.rpg-panel[data-theme="cyberpunk"] .rpg-tab-btn.active {
+.rpg-panel[data-theme] .rpg-tab-btn.active {
background: var(--rpg-highlight);
border-color: var(--rpg-highlight);
color: var(--rpg-bg);
}
-/* Apply theme colors to inventory subtabs */
-.rpg-panel[data-theme="sci-fi"] .rpg-inventory-subtabs,
-.rpg-panel[data-theme="fantasy"] .rpg-inventory-subtabs,
-.rpg-panel[data-theme="cyberpunk"] .rpg-inventory-subtabs {
+/* Inventory subtabs theming */
+.rpg-panel[data-theme] .rpg-inventory-subtabs {
border-bottom-color: var(--rpg-border);
}
-.rpg-panel[data-theme="sci-fi"] .rpg-inventory-subtab,
-.rpg-panel[data-theme="fantasy"] .rpg-inventory-subtab,
-.rpg-panel[data-theme="cyberpunk"] .rpg-inventory-subtab {
+.rpg-panel[data-theme] .rpg-inventory-subtab {
border-color: var(--rpg-border);
color: var(--rpg-text);
}
-.rpg-panel[data-theme="sci-fi"] .rpg-inventory-subtab:hover,
-.rpg-panel[data-theme="fantasy"] .rpg-inventory-subtab:hover,
-.rpg-panel[data-theme="cyberpunk"] .rpg-inventory-subtab:hover {
+.rpg-panel[data-theme] .rpg-inventory-subtab:hover {
border-color: var(--rpg-highlight);
color: var(--rpg-highlight);
}
-.rpg-panel[data-theme="sci-fi"] .rpg-inventory-subtab.active,
-.rpg-panel[data-theme="fantasy"] .rpg-inventory-subtab.active,
-.rpg-panel[data-theme="cyberpunk"] .rpg-inventory-subtab.active {
+.rpg-panel[data-theme] .rpg-inventory-subtab.active {
border-color: var(--rpg-highlight);
color: var(--rpg-highlight);
}
-/* Apply theme colors to quests subtabs */
-.rpg-panel[data-theme="sci-fi"] .rpg-quests-subtabs,
-.rpg-panel[data-theme="fantasy"] .rpg-quests-subtabs,
-.rpg-panel[data-theme="cyberpunk"] .rpg-quests-subtabs {
+/* Quests subtabs theming */
+.rpg-panel[data-theme] .rpg-quests-subtabs {
border-bottom-color: var(--rpg-border);
}
-.rpg-panel[data-theme="sci-fi"] .rpg-quests-subtab,
-.rpg-panel[data-theme="fantasy"] .rpg-quests-subtab,
-.rpg-panel[data-theme="cyberpunk"] .rpg-quests-subtab {
+.rpg-panel[data-theme] .rpg-quests-subtab {
border-color: var(--rpg-border);
color: var(--rpg-text);
}
-.rpg-panel[data-theme="sci-fi"] .rpg-quests-subtab:hover,
-.rpg-panel[data-theme="fantasy"] .rpg-quests-subtab:hover,
-.rpg-panel[data-theme="cyberpunk"] .rpg-quests-subtab:hover {
+.rpg-panel[data-theme] .rpg-quests-subtab:hover {
border-color: var(--rpg-highlight);
color: var(--rpg-highlight);
}
-.rpg-panel[data-theme="sci-fi"] .rpg-quests-subtab.active,
-.rpg-panel[data-theme="fantasy"] .rpg-quests-subtab.active,
-.rpg-panel[data-theme="cyberpunk"] .rpg-quests-subtab.active {
+.rpg-panel[data-theme] .rpg-quests-subtab.active {
border-color: var(--rpg-highlight);
color: var(--rpg-highlight);
}
-/* Apply theme colors to storage locations */
-.rpg-panel[data-theme="sci-fi"] .rpg-storage-location,
-.rpg-panel[data-theme="fantasy"] .rpg-storage-location,
-.rpg-panel[data-theme="cyberpunk"] .rpg-storage-location {
+/* Storage locations theming */
+.rpg-panel[data-theme] .rpg-storage-location {
border-color: var(--rpg-border);
}
-.rpg-panel[data-theme="sci-fi"] .rpg-storage-header,
-.rpg-panel[data-theme="fantasy"] .rpg-storage-header,
-.rpg-panel[data-theme="cyberpunk"] .rpg-storage-header {
+.rpg-panel[data-theme] .rpg-storage-header {
background: var(--rpg-highlight);
}
-.rpg-panel[data-theme="sci-fi"] .rpg-storage-content,
-.rpg-panel[data-theme="fantasy"] .rpg-storage-content,
-.rpg-panel[data-theme="cyberpunk"] .rpg-storage-content {
+.rpg-panel[data-theme] .rpg-storage-content {
background: var(--rpg-accent);
}
-/* Apply theme colors to buttons */
-.rpg-panel[data-theme="sci-fi"] .rpg-inventory-edit-btn,
-.rpg-panel[data-theme="sci-fi"] .rpg-inventory-add-btn,
-.rpg-panel[data-theme="sci-fi"] .rpg-inventory-remove-btn,
-.rpg-panel[data-theme="sci-fi"] .rpg-quest-edit,
-.rpg-panel[data-theme="sci-fi"] .rpg-quest-remove,
-.rpg-panel[data-theme="sci-fi"] .rpg-add-quest-btn,
-.rpg-panel[data-theme="fantasy"] .rpg-inventory-edit-btn,
-.rpg-panel[data-theme="fantasy"] .rpg-inventory-add-btn,
-.rpg-panel[data-theme="fantasy"] .rpg-inventory-remove-btn,
-.rpg-panel[data-theme="fantasy"] .rpg-quest-edit,
-.rpg-panel[data-theme="fantasy"] .rpg-quest-remove,
-.rpg-panel[data-theme="fantasy"] .rpg-add-quest-btn,
-.rpg-panel[data-theme="cyberpunk"] .rpg-inventory-edit-btn,
-.rpg-panel[data-theme="cyberpunk"] .rpg-inventory-add-btn,
-.rpg-panel[data-theme="cyberpunk"] .rpg-inventory-remove-btn,
-.rpg-panel[data-theme="cyberpunk"] .rpg-quest-edit,
-.rpg-panel[data-theme="cyberpunk"] .rpg-quest-remove,
-.rpg-panel[data-theme="cyberpunk"] .rpg-add-quest-btn {
+/* Button theming */
+.rpg-panel[data-theme] .rpg-inventory-edit-btn,
+.rpg-panel[data-theme] .rpg-inventory-add-btn,
+.rpg-panel[data-theme] .rpg-inventory-remove-btn,
+.rpg-panel[data-theme] .rpg-quest-edit,
+.rpg-panel[data-theme] .rpg-quest-remove,
+.rpg-panel[data-theme] .rpg-add-quest-btn {
background: var(--rpg-accent);
border-color: var(--rpg-border);
color: var(--rpg-text);
}
-.rpg-panel[data-theme="sci-fi"] .rpg-inventory-add-btn,
-.rpg-panel[data-theme="sci-fi"] .rpg-add-quest-btn,
-.rpg-panel[data-theme="fantasy"] .rpg-inventory-add-btn,
-.rpg-panel[data-theme="fantasy"] .rpg-add-quest-btn,
-.rpg-panel[data-theme="cyberpunk"] .rpg-inventory-add-btn,
-.rpg-panel[data-theme="cyberpunk"] .rpg-add-quest-btn {
+.rpg-panel[data-theme] .rpg-inventory-add-btn,
+.rpg-panel[data-theme] .rpg-add-quest-btn {
background: transparent;
border-color: var(--rpg-highlight);
color: var(--rpg-highlight);
}
-/* Apply theme colors to inventory/quest items */
-.rpg-panel[data-theme="sci-fi"] .rpg-inventory-text,
-.rpg-panel[data-theme="sci-fi"] .rpg-quest-item,
-.rpg-panel[data-theme="sci-fi"] .rpg-main-quest-display,
-.rpg-panel[data-theme="fantasy"] .rpg-inventory-text,
-.rpg-panel[data-theme="fantasy"] .rpg-quest-item,
-.rpg-panel[data-theme="fantasy"] .rpg-main-quest-display,
-.rpg-panel[data-theme="cyberpunk"] .rpg-inventory-text,
-.rpg-panel[data-theme="cyberpunk"] .rpg-quest-item,
-.rpg-panel[data-theme="cyberpunk"] .rpg-main-quest-display {
+/* Inventory/quest items theming */
+.rpg-panel[data-theme] .rpg-inventory-text,
+.rpg-panel[data-theme] .rpg-quest-item,
+.rpg-panel[data-theme] .rpg-main-quest-display {
background: var(--rpg-accent);
border-color: var(--rpg-border);
color: var(--rpg-text);
}
-.rpg-panel[data-theme="sci-fi"] .rpg-quest-item:hover,
-.rpg-panel[data-theme="fantasy"] .rpg-quest-item:hover,
-.rpg-panel[data-theme="cyberpunk"] .rpg-quest-item:hover {
+.rpg-panel[data-theme] .rpg-quest-item:hover {
border-color: var(--rpg-highlight);
}
-/* Apply theme colors to hints and empty states */
-.rpg-panel[data-theme="sci-fi"] .rpg-inventory-hint,
-.rpg-panel[data-theme="sci-fi"] .rpg-quest-hint,
-.rpg-panel[data-theme="sci-fi"] .rpg-inventory-empty,
-.rpg-panel[data-theme="sci-fi"] .rpg-quest-empty,
-.rpg-panel[data-theme="fantasy"] .rpg-inventory-hint,
-.rpg-panel[data-theme="fantasy"] .rpg-quest-hint,
-.rpg-panel[data-theme="fantasy"] .rpg-inventory-empty,
-.rpg-panel[data-theme="fantasy"] .rpg-quest-empty,
-.rpg-panel[data-theme="cyberpunk"] .rpg-inventory-hint,
-.rpg-panel[data-theme="cyberpunk"] .rpg-quest-hint,
-.rpg-panel[data-theme="cyberpunk"] .rpg-inventory-empty,
-.rpg-panel[data-theme="cyberpunk"] .rpg-quest-empty {
+/* Hints and empty states theming */
+.rpg-panel[data-theme] .rpg-inventory-hint,
+.rpg-panel[data-theme] .rpg-quest-hint,
+.rpg-panel[data-theme] .rpg-inventory-empty,
+.rpg-panel[data-theme] .rpg-quest-empty {
border-color: var(--rpg-highlight);
color: var(--rpg-text);
}
-/* Apply theme colors to input fields */
-.rpg-panel[data-theme="sci-fi"] .rpg-inline-input,
-.rpg-panel[data-theme="sci-fi"] .rpg-quest-edit-form input,
-.rpg-panel[data-theme="sci-fi"] .rpg-quest-edit-form textarea,
-.rpg-panel[data-theme="fantasy"] .rpg-inline-input,
-.rpg-panel[data-theme="fantasy"] .rpg-quest-edit-form input,
-.rpg-panel[data-theme="fantasy"] .rpg-quest-edit-form textarea,
-.rpg-panel[data-theme="cyberpunk"] .rpg-inline-input,
-.rpg-panel[data-theme="cyberpunk"] .rpg-quest-edit-form input,
-.rpg-panel[data-theme="cyberpunk"] .rpg-quest-edit-form textarea {
+/* Input fields theming */
+.rpg-panel[data-theme] .rpg-inline-input,
+.rpg-panel[data-theme] .rpg-quest-edit-form input,
+.rpg-panel[data-theme] .rpg-quest-edit-form textarea {
background: var(--rpg-bg);
border-color: var(--rpg-border);
color: var(--rpg-text);
}
-.rpg-panel[data-theme="sci-fi"] .rpg-inline-input:focus,
-.rpg-panel[data-theme="sci-fi"] .rpg-quest-edit-form input:focus,
-.rpg-panel[data-theme="sci-fi"] .rpg-quest-edit-form textarea:focus,
-.rpg-panel[data-theme="fantasy"] .rpg-inline-input:focus,
-.rpg-panel[data-theme="fantasy"] .rpg-quest-edit-form input:focus,
-.rpg-panel[data-theme="fantasy"] .rpg-quest-edit-form textarea:focus,
-.rpg-panel[data-theme="cyberpunk"] .rpg-inline-input:focus,
-.rpg-panel[data-theme="cyberpunk"] .rpg-quest-edit-form input:focus,
-.rpg-panel[data-theme="cyberpunk"] .rpg-quest-edit-form textarea:focus {
+.rpg-panel[data-theme] .rpg-inline-input:focus,
+.rpg-panel[data-theme] .rpg-quest-edit-form input:focus,
+.rpg-panel[data-theme] .rpg-quest-edit-form textarea:focus {
border-color: var(--rpg-highlight);
}
-
/* ============================================ */
/* _09_dice-modal.css */
/* ============================================ */
@@ -6881,8 +6778,6 @@ body:has(.rpg-panel.rpg-mobile-open) .rpg-fab-widget-container {
gap: 1rem;
padding: 0.5rem;
font-size: 0.9rem;
- content-visibility: auto;
- contain-intrinsic-size: 0 500px;
}
/* Sub-tabs Navigation */
@@ -12081,8 +11976,6 @@ body.documentstyle .rpg-inline-thoughts {
.rpg-equipment-container {
padding: 8px 0;
- content-visibility: auto;
- contain-intrinsic-size: 0 300px;
}
.rpg-equipment-header {