Compare commits

...
13 Commits
Author SHA1 Message Date
surtic86 dc927062a3 stff 2026-10-15 08:49:11 +02:00
surtic86andClaude Opus 5.5 70640da6f2 fix: lighter placeholders and regular-weight sidebar links
Placeholders use the outline color, so an empty field no longer looks
filled.

Sidebar link lists (queries, versions, wiki index, admin menu) use
regular body text; only the section titles stay bold. Each item gets
8px vertical padding and a 2px gap, so wrapped names stay apart.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:24:08 +02:00
surtic86andClaude Opus 5.5 44084eaffa fix: contrast of progress bars, selections and selected query
At medium and high contrast, secondary-container turns dark. The empty
part of progress bars, highlighted code lines and a targeted roadmap
header now use surface-container-highest, so the fill and the text stay
readable. The selected project in the project jump is light at these
levels too; its check mark marks it.

The selected sidebar query gets the pill style even with its "clear"
icon next to it. The icon sits inside the pill and takes the pill's
text color.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 09:43:14 +02:00
Andreas Reinhold / reiniandClaude Opus 5.5 db58c6e86f fix: align form field widths across the app
From 900px up, selects and text fields fill their column up to 480px,
numbers and dates are 140px, and long text stays full width, so every
form shares one right edge. Below 900px every field runs full width.
Overrides core's id-pinned widths (% Done, mobile dates, settings
textareas). check-form-alignment.mjs now checks widths at 1440, 1024
and 390px across all settings tabs and admin forms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 14:12:44 +02:00
Andreas Reinhold / reiniandClaude Opus 5.5 20b7269df2 feat: redmine_tonalmine plugin for default and per-account appearance
Admins set the default mode, contrast and color on Settings > Display,
shown as three labelled form rows below the Theme select. Logged-in
users' choices are saved on their account and override only the parts
they changed; anonymous visitors get the default.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 14:12:44 +02:00
Andreas Reinhold / reiniandClaude Opus 5.5 77040e24a8 fix: calendar month bar and mobile agenda layout
The month and year selects sit between previous/next icon buttons on one
row, with Apply / Clear / Save after them, on all widths. On mobile the
calendar becomes an agenda: date column beside the issues, days of
neighbouring months hidden, empty days collapsed to one line, lighter
week headers and a highlighted today row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 13:06:00 +02:00
Andreas Reinhold / reiniandClaude Opus 5.5 d687084cf0 fix: full-width mobile query filters and tonal "Add filter" button
On mobile the filter operator and value fields span the row instead of
fixed desktop widths, and empty value rows are hidden. "Add filter"
becomes an M3 tonal button on all widths so it stands out from the
outlined filter fields.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 12:53:02 +02:00
Andreas Reinhold / reiniandClaude Opus 5.5 906153014b fix: structure calendar/Gantt hover cards into title and field grid
- Regroup Redmine's "<strong>Label</strong>: value<br>" tooltip lines on
  first hover into a title block and a label/value grid (theme.js).
- Style the issue link as a kicker, the subject as title-small, and the
  labels as a muted column next to aligned values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 12:44:19 +02:00
Andreas Reinhold / reiniandClaude Opus 5.5 6c271ffffe feat: contrast levels and custom color picker in appearance menu
Generate medium/high contrast schemes, add a custom swatch that builds a scheme
in the browser via lazy-loaded scheme.js, allow Theme Builder exports as palettes.
Drop the teal palette and spread the swatches across the menu width.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 12:40:34 +02:00
Andreas Reinhold / reiniandClaude Opus 5.5 b4eba7ae63 chore: mount local Redmine plugins in dev container
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 12:40:34 +02:00
Andreas Reinhold / reiniandClaude Opus 5.5 a79c9404d5 fix: style calendar/Gantt hover cards and plain tooltips for Material 3
- Theme div.tooltip span.tip as an M3 rich tooltip (surface-container,
  elevation, rounded corners, body-medium text).
- Raise .ui-tooltip specificity so jQuery UI's .ui-widget-content rule
  no longer overrides the inverse-surface colors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 11:35:04 +02:00
Andreas Reinhold / reiniandClaude Opus 5.5 0e9ce26a1a fix: mobile drawer width, group dividers and sidebar form layout
- Drawer uses the M3 width (up to 360px) so the appearance picker fits;
  segmented buttons get equal widths and swatches no longer shrink.
- Dividers separate the drawer groups, so section headings no longer
  read as nav items.
- Sidebar forms in the drawer (e.g. activity filters) line up with the
  nav labels, fields stack under their label, checkbox rows align.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 11:28:25 +02:00
Andreas Reinhold / reiniandClaude Opus 5.5 b6654f31e7 fix: mobile navigation drawer overlays content instead of pushing it
Core's responsive.css shifts #wrapper and #header aside when the flyout
opens. Make it an M3 modal drawer: fixed over the page with a scrim that
closes it on tap via core's existing #main click handler.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 11:22:13 +02:00
22 changed files with 1158 additions and 136 deletions
+1
View File
@@ -3,3 +3,4 @@ screenshots/
.DS_Store .DS_Store
tools/.cache/ tools/.cache/
docs/plans/ docs/plans/
dev/plugins/
+30 -6
View File
@@ -8,6 +8,10 @@ Material Design 3 theme for Redmine 7.0.
2. Restart Redmine (theme assets are compiled at boot). 2. Restart Redmine (theme assets are compiled at boot).
3. Select **Tonalmine** in Administration → Settings → Display → Theme. 3. Select **Tonalmine** in Administration → Settings → Display → Theme.
Optional: the `redmine_tonalmine` plugin adds a default appearance for administrators and saves each
user's choice on their account. Copy the `redmine_tonalmine/` folder into `<redmine>/plugins/` and
restart Redmine (no migrations).
## Development ## Development
Requires Docker. Requires Docker.
@@ -48,9 +52,22 @@ the map, so run it after every Redmine update.
### Colors ### Colors
Each user picks a mode (Auto / Light / Dark) and a color (Purple, Blue, Teal, Green, Orange) in the Each user picks a mode (Auto / Light / Dark), a contrast level (Standard / Medium / High) and a color (Purple, Blue, Green, Orange) in the
**Appearance** section of the profile menu (top right; in the mobile menu at the bottom). The choice is **Appearance** section of the profile menu (top right; in the mobile menu at the bottom). Auto follows
stored per browser (`localStorage`); Auto follows the operating system. the operating system. Without the plugin, the choice is stored per browser (`localStorage`).
With the `redmine_tonalmine` plugin:
- Administrators set the default appearance in Administration → Settings → Display, below the Theme
select. It shows while Tonalmine is selected; when switching from another theme, save once first.
- Logged-in users' choices are saved on their account (`pref[:tonalmine]`) and follow them to every
browser. Only the parts a user changed override the default; **Reset to default** clears them.
A choice already stored in the browser is moved to the account on the first visit.
- Anonymous visitors get the default; their own choice is still stored per browser.
`node e2e/check-appearance.mjs` checks this end to end (it empties the default when done). The last (rainbow) swatch
lets users pick any color: `javascripts/scheme.js` is loaded on demand, generates the scheme in the
browser, and the generated CSS is stored with the choice, so later page loads don't need it.
The color roles are generated from one seed color per palette with `@material/material-color-utilities` The color roles are generated from one seed color per palette with `@material/material-color-utilities`
(scheme TonalSpot) into `tonalmine/stylesheets/palettes.css`: (scheme TonalSpot) into `tonalmine/stylesheets/palettes.css`:
@@ -58,12 +75,19 @@ The color roles are generated from one seed color per palette with `@material/ma
```sh ```sh
cd tools/palettes cd tools/palettes
npm install # once npm install # once
npm run build # writes palettes.css npm run build # writes palettes.css and javascripts/scheme.js
``` ```
To add or change a palette, edit `PALETTES` in `tools/palettes/build_palettes.mjs` and in To add or change a palette, edit `PALETTES` in `tools/palettes/build_palettes.mjs` and in
`tonalmine/javascripts/theme.js` (the build fails if they differ), then rebuild. The only other `tonalmine/javascripts/theme.js` (the build fails if they differ), then rebuild.
hard-coded colors are the select chevrons `images/arrow-drop-down*.svg` (one per mode).
To use a scheme from [Material Theme Builder](https://material-foundation.github.io/material-theme-builder/)
as-is: export it as Web (CSS), copy its `light.css` and `dark.css` into `tools/palettes/themes/<name>/`,
and also its `light-mc.css`, `light-hc.css`, `dark-mc.css` and `dark-hc.css` contrast files. Then add
`['<name>', '<seed>', 'themes/<name>']` to `PALETTES`. The roles are copied from the export; the seed
only tints the success/warning colors.
The only other hard-coded colors are the select chevrons `images/arrow-drop-down*.svg` (one per mode).
## License ## License
+2
View File
@@ -7,6 +7,8 @@ services:
- redmine-sqlite:/usr/src/redmine/sqlite - redmine-sqlite:/usr/src/redmine/sqlite
- redmine-files:/usr/src/redmine/files - redmine-files:/usr/src/redmine/files
- ../tonalmine:/usr/src/redmine/themes/tonalmine - ../tonalmine:/usr/src/redmine/themes/tonalmine
- ./plugins:/usr/src/redmine/plugins
- ../redmine_tonalmine:/usr/src/redmine/plugins/redmine_tonalmine
- .:/seed:ro - .:/seed:ro
volumes: volumes:
+167
View File
@@ -0,0 +1,167 @@
// Checks the redmine_tonalmine plugin: the admin default appearance (Display settings tab), users'
// choices saved on their account, Reset to default, the one-time upload of a localStorage choice,
// and the endpoint's validation. Needs the plugin mounted (dev/docker-compose.yml) and the seed users.
// Usage: node check-appearance.mjs (exits 1 on any failure; leaves the admin default empty)
import { chromium } from 'playwright';
const BASE = process.env.REDMINE_URL ?? 'http://127.0.0.1:3070';
const browser = await chromium.launch();
let failures = 0;
function check(name, ok, detail = '') {
if (!ok) failures++;
console.log(`${ok ? 'ok ' : 'FAIL'} ${name}${ok ? '' : `\n ${detail}`}`);
}
async function login(user, password = 'password123', setup) {
const context = await browser.newContext({ viewport: { width: 1440, height: 900 } });
const page = await context.newPage();
await page.goto(`${BASE}/login`);
if (setup) await page.evaluate(setup);
await page.fill('#username', user);
await page.fill('#password', password);
await page.click('#login-submit');
await page.waitForURL((url) => !url.pathname.startsWith('/login'));
await page.waitForLoadState('networkidle');
return page;
}
// The <html> attributes theme.js sets; absent means the built-in default (auto, standard, purple).
const looks = (page) => page.evaluate(() => {
const d = document.documentElement.dataset;
return `${d.mode ?? 'auto'} ${d.contrast ?? 'standard'} ${d.palette ?? 'purple'}`;
});
async function expectLook(name, page, want) {
const got = await looks(page);
check(name, got === want, `expected "${want}", got "${got}"`);
}
const put = (page, body, withToken = true) => page.evaluate(async ([body, withToken]) => {
const headers = { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' };
if (withToken) headers['X-CSRF-Token'] = document.querySelector('meta[name="csrf-token"]').content;
const response = await fetch('/tonalmine/appearance', { method: 'PUT', headers, body: JSON.stringify(body) });
return response.status;
}, [body, withToken]);
// Clicks a picker control in the profile menu and waits until the account has it.
async function pick(page, selector) {
const saved = page.waitForResponse((r) => r.url().includes('/tonalmine/appearance'));
await page.evaluate((s) => document.querySelector(`#account ${s}`).click(), selector);
return (await saved).status();
}
// Picks a custom color the way the color dialog does (input while dragging, change at the end).
const pickColor = (page, scope, seed) => page.evaluate(([scope, seed]) => {
const input = document.querySelector(`${scope} [data-nm-custom]`);
input.value = seed;
input.dispatchEvent(new Event('input', { bubbles: true }));
input.dispatchEvent(new Event('change', { bubbles: true }));
}, [scope, seed]);
const customCss = (page) => page.evaluate(() => document.getElementById('nm-custom-palette')?.textContent.length ?? 0);
async function saveDefault(page, change) {
await page.goto(`${BASE}/settings?tab=display`);
await change();
await Promise.all([page.waitForURL(/tab=display/), page.click('#tab-content-display form [type=submit]')]);
await page.waitForLoadState('networkidle');
}
const resetUser = async (user) => {
const page = await login(user);
await put(page, {});
await page.context().close();
};
await Promise.all(['jdoe', 'msmith', 'pweber'].map(resetUser));
// 1. Admin default on the Display settings tab
const admin = await login('admin', 'admin');
await admin.goto(`${BASE}/settings?tab=display`);
// Mode, contrast and color are three form rows
const rowsShown = () => admin.$$eval('p:has([data-nm-default])', (rows) => rows.length === 3 && rows.map((r) => !!r.offsetParent));
check('default picker shown while Tonalmine is selected', (await rowsShown())?.every(Boolean));
await admin.selectOption('#settings_ui_theme', '');
check('default picker hidden and not posted for another theme', (await rowsShown())?.every((shown) => !shown) &&
(await admin.$$eval('[data-nm-default-input]', (inputs) => inputs.every((i) => i.disabled))));
await saveDefault(admin, async () => {
await admin.click('[data-nm-default] [data-nm-palette="blue"]');
await admin.click('[data-nm-default] [data-nm-mode="dark"]');
});
check('default picker shows the saved default', await admin.evaluate(() =>
document.querySelector('[data-nm-default] [data-nm-palette="blue"]').getAttribute('aria-pressed') === 'true' &&
document.querySelector('[data-nm-default] [data-nm-mode="dark"]').getAttribute('aria-pressed') === 'true'));
// 2. A user without a choice gets the default
const jdoe = await login('jdoe');
await expectLook('user without a choice gets the default', jdoe, 'dark standard blue');
// 3. The user's choice is saved on the account and only overrides that part
check('choosing Light is saved (204)', (await pick(jdoe, '[data-nm-mode="light"]')) === 204);
const jdoe2 = await login('jdoe');
await expectLook('choice follows the user to another browser', jdoe2, 'light standard blue');
// 4. Parts the user didn't change follow a new default
await saveDefault(admin, () => admin.click('[data-nm-default] [data-nm-palette="green"]'));
await jdoe2.reload({ waitUntil: 'networkidle' });
await expectLook('unchanged parts follow the new default', jdoe2, 'light standard green');
// 5. Reset to default
check('Reset to default is shown after a change', await jdoe2.isVisible('#account [data-nm-reset]') ||
await jdoe2.evaluate(() => !document.querySelector('#account [data-nm-reset]').hidden));
check('reset is saved (204)', (await pick(jdoe2, '[data-nm-reset]')) === 204);
await expectLook('reset returns to the default', jdoe2, 'dark standard green');
await jdoe2.reload({ waitUntil: 'networkidle' });
await expectLook('reset is kept after reload', jdoe2, 'dark standard green');
check('Reset to default is hidden without a choice', await jdoe2.evaluate(() => document.querySelector('#account [data-nm-reset]').hidden));
// 5b. A custom color, with its generated CSS, is saved on the account
const savedColor = jdoe2.waitForResponse((r) => r.url().includes('/tonalmine/appearance'));
await pickColor(jdoe2, '#account', '#c0392b');
check('custom color is saved (204)', (await savedColor).status() === 204);
const jdoe3 = await login('jdoe');
await expectLook('custom color follows the user', jdoe3, 'dark standard custom');
check('custom color CSS comes from the account', (await customCss(jdoe3)) > 1000);
await pick(jdoe3, '[data-nm-reset]');
// 6. A localStorage choice from before the plugin moves to the account once
const msmith = await login('msmith', 'password123', () => localStorage.setItem('tonalmine', '{"contrast":"high"}'));
await expectLook('localStorage choice is applied after login', msmith, 'dark high green');
check('localStorage is cleared for logged-in users', await msmith.evaluate(() => localStorage.getItem('tonalmine') === null));
const msmith2 = await login('msmith');
await expectLook('uploaded choice is on the account', msmith2, 'dark high green');
// 7. Validation; the request without a CSRF token goes last, since Redmine then ends the session
const pweber = await login('pweber');
check('valid choice is saved (204)', (await put(pweber, { contrast: 'medium' })) === 204);
for (const body of [{ mode: 'neon' }, { palette: 'custom' }, { palette: 'custom', seed: '#123456', css: '</style>' }, { foo: 1 }, { mode: 5 }]) {
const status = await put(pweber, body);
check(`invalid ${JSON.stringify(body)} is refused (422)`, status === 422, `got ${status}`);
}
const tokenless = await put(pweber, { contrast: 'high' }, false);
check('request without CSRF token is refused (422)', tokenless === 422, `got ${tokenless}`);
const pweber2 = await login('pweber');
await expectLook('refused requests left the choice unchanged', pweber2, 'dark medium green');
// 8. Anonymous visitors get the default
const anonymous = await (await browser.newContext()).newPage();
await anonymous.goto(`${BASE}/login`);
await expectLook('anonymous visitor gets the default', anonymous, 'dark standard green');
// 8b. A custom default color reaches everyone
await saveDefault(admin, async () => {
await pickColor(admin, '[data-nm-default]', '#1e88e5');
await admin.waitForFunction(() => document.querySelector('[data-nm-default-input="css"]').value.length > 1000);
});
await anonymous.reload();
await expectLook('custom default reaches anonymous visitors', anonymous, 'dark standard custom');
check('custom default CSS is rendered', (await customCss(anonymous)) > 1000);
// 9. Clean up: empty default, so other checks and screenshots see the built-in look
await saveDefault(admin, () => admin.$$eval('[data-nm-default-input]', (inputs) => inputs.forEach((i) => { i.value = ''; })));
await anonymous.reload();
await expectLook('empty default restores the built-in look', anonymous, 'auto standard purple');
await Promise.all(['jdoe', 'msmith', 'pweber'].map(resetUser));
await browser.close();
process.exit(failures ? 1 : 0);
+70 -34
View File
@@ -1,47 +1,83 @@
// Checks that each form label (.tabular forms, query filters) lines up with the field next to it. // Checks .tabular forms and query filters at desktop, tablet landscape and phone width:
// - each form label lines up with the field next to it (not on phones, where labels stack)
// - field widths follow the three tracks (components.css): standard fields fill the column up
// to 480px, compact fields (numbers, dates) are 140px, long-text fields fill the column;
// on phones every field fills its row
// Usage: node check-form-alignment.mjs (exits 1 if any row is off by more than 2px) // Usage: node check-form-alignment.mjs (exits 1 if any row is off by more than 2px)
import { chromium } from 'playwright'; import { chromium } from 'playwright';
const BASE = process.env.REDMINE_URL ?? 'http://127.0.0.1:3070'; const BASE = process.env.REDMINE_URL ?? 'http://127.0.0.1:3070';
const settingsTabs = ['general', 'display', 'authentication', 'api', 'projects', 'users', 'issues', 'timelog', 'attachments', 'mail_handler', 'repositories'];
const pages = [ const pages = [
'/projects?display_type=list', '/projects/apollo/issues', '/projects/apollo/time_entries', '/projects/apollo/issues/new', '/issues/13/edit', '/issues/bulk_edit?ids[]=13&ids[]=17', '/projects?display_type=list', '/projects/apollo/issues', '/projects/apollo/time_entries', '/projects/apollo/issues/new', '/issues/13/edit', '/issues/bulk_edit?ids[]=13&ids[]=17',
'/projects/apollo/time_entries/new', '/settings', '/my/account', '/users/5/edit', '/projects/apollo/time_entries/new', '/my/account', '/my/password', '/users/5/edit',
'/projects/apollo/settings', '/custom_fields/1/edit', '/projects/apollo/wiki/Wiki/edit', '/projects/apollo/settings', '/projects/apollo/settings/issues', '/custom_fields/1/edit', '/projects/apollo/wiki/Wiki/edit',
'/projects/apollo/wiki/Wiki/rename', '/projects/apollo/versions/new', '/projects/apollo/news/new', '/projects/apollo/documents/new',
'/projects/apollo/queries/new', '/projects/new', '/roles/new', '/trackers/new', '/groups/new',
...settingsTabs.map((tab) => `/settings?tab=${tab}`),
]; ];
const browser = await chromium.launch(); const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
await page.goto(`${BASE}/login`);
await page.fill('#username', 'admin');
await page.fill('#password', 'admin');
await page.click('#login-submit');
await page.waitForURL((url) => !url.pathname.startsWith('/login'));
let failures = 0; let failures = 0;
for (const path of pages) { for (const width of [1440, 1024, 390]) {
await page.goto(`${BASE}${path}`, { waitUntil: 'networkidle' }); const page = await browser.newPage({ viewport: { width, height: 900 } });
const rows = await page.evaluate(() => [ await page.goto(`${BASE}/login`);
// Query filters: label text vs. operator select await page.fill('#username', 'admin');
...[...document.querySelectorAll('#filters-table .filter')].flatMap((row) => { await page.fill('#password', 'admin');
const label = row.querySelector('.field label'), control = row.querySelector('.operator select'); await page.click('#login-submit');
if (!label || !control || !control.offsetParent) return []; await page.waitForURL((url) => !url.pathname.startsWith('/login'));
const l = label.getBoundingClientRect(), c = control.getBoundingClientRect();
return [{ label: `filter ${label.textContent.trim().slice(0, 22)}`, offset: Math.round(l.top + l.height / 2 - (c.top + c.height / 2)) }]; for (const path of pages) {
}), await page.goto(`${BASE}${path}`, { waitUntil: 'networkidle' });
...[...document.querySelectorAll('.tabular p')].flatMap((row) => { const rows = await page.evaluate((stacked) => {
const label = row.querySelector(':scope > label:not(.floating):not(.block):not(.inline):not(.no-css)'); const visible = (el) => el && el.offsetParent;
const control = row.querySelector(':scope > input:not([type=hidden]), :scope > select, :scope > span > input:not([type=hidden]), :scope > span > select'); const out = [];
if (!label || !control || !label.offsetParent || !control.offsetParent) return []; if (!stacked) {
const style = getComputedStyle(label); // Query filters: label text vs. operator select
const labelBox = label.getBoundingClientRect(); for (const row of document.querySelectorAll('#filters-table .filter')) {
const controlBox = control.getBoundingClientRect(); const label = row.querySelector('.field label'), control = row.querySelector('.operator select');
const labelMiddle = labelBox.top + parseFloat(style.paddingTop) + parseFloat(style.lineHeight) / 2; if (!label || !visible(control)) continue;
return [{ label: label.textContent.trim().slice(0, 30), offset: Math.round(labelMiddle - (controlBox.top + controlBox.height / 2)) }]; const l = label.getBoundingClientRect(), c = control.getBoundingClientRect();
}), out.push({ label: `filter ${label.textContent.trim().slice(0, 22)}`, offset: Math.round(l.top + l.height / 2 - (c.top + c.height / 2)) });
]); }
const bad = rows.filter((r) => Math.abs(r.offset) > 2); // Labels vs. fields (and the default appearance groups on the Display tab)
failures += bad.length; for (const row of document.querySelectorAll('.tabular p')) {
console.log(`${bad.length ? 'FAIL' : 'ok '} ${path} (${rows.length} rows)${bad.map((r) => `\n ${r.label}: ${r.offset}px`).join('')}`); const label = row.querySelector(':scope > label:not(.floating):not(.block):not(.inline):not(.no-css)');
const control = row.querySelector(':scope > input:not([type=hidden]), :scope > select, :scope > span > input:not([type=hidden]), :scope > span > select, :scope > .nm-appearance > [role="group"]');
if (!visible(label) || !visible(control)) continue;
const style = getComputedStyle(label);
const labelBox = label.getBoundingClientRect();
const controlBox = control.getBoundingClientRect();
const labelMiddle = labelBox.top + parseFloat(style.paddingTop) + parseFloat(style.lineHeight) / 2;
out.push({ label: label.textContent.trim().slice(0, 30), offset: Math.round(labelMiddle - (controlBox.top + controlBox.height / 2)) });
}
}
// Field widths
const textLike = 'textarea, select:not([multiple]), input:is([type="text"], [type="password"], [type="email"], [type="url"], [type="search"], [type="tel"], [type="number"], [type="date"], :not([type]))';
const compact = 'input:is([type="number"], [type="date"], .date, [size="1"], [size="2"], [size="3"], [size="4"], [size="5"], [size="6"])';
const full = 'textarea, input:is(.string_cf, .link_cf, #issue_subject, #document_title, #content_comments, #time_entry_comments)';
for (const field of document.querySelectorAll(`.tabular p > :is(${textLike}), .tabular p > span > :is(${textLike})`)) {
if (!visible(field)) continue;
// The project select shares its row with the floated "Private" checkbox
if (field.id === 'issue_project_id') continue;
const row = field.closest('p');
const style = getComputedStyle(row);
const column = row.clientWidth - parseFloat(style.paddingInlineStart) - parseFloat(style.paddingInlineEnd);
const icon = field.nextElementSibling?.matches('.icon-only') ? 36 : 0;
const expected = stacked || field.matches(full) ? column - icon
: field.matches(compact) ? 140
: Math.min(480, column) - icon;
const actual = field.getBoundingClientRect().width;
const name = row.querySelector(':scope > label')?.textContent.trim().slice(0, 30) || field.id || field.name;
out.push({ label: `width ${name}`, offset: Math.round(actual - expected) });
}
return out;
}, width < 900);
const bad = rows.filter((r) => Math.abs(r.offset) > 2);
failures += bad.length;
console.log(`${bad.length ? 'FAIL' : 'ok '} ${width} ${path} (${rows.length} checks)${bad.map((r) => `\n ${r.label}: ${r.offset}px`).join('')}`);
}
await page.close();
} }
await browser.close(); await browser.close();
@@ -0,0 +1,17 @@
# Saves the current user's appearance (sent by theme.js). The body replaces the whole saved
# choice; an empty object resets the user to the default.
class TonalmineAppearanceController < ApplicationController
before_action :require_login
def update
prefs = params.to_unsafe_h.except('controller', 'action', 'tonalmine_appearance')
errors = RedmineTonalmine::Appearance.errors(prefs)
if errors.any?
render json: {errors: errors}, status: :unprocessable_content
else
User.current.pref[:tonalmine] = RedmineTonalmine::Appearance.clean(prefs)
User.current.pref.save
head :no_content
end
end
end
+3
View File
@@ -0,0 +1,3 @@
# No format segment: Redmine treats .json requests as API calls, which skip the session and the
# CSRF check. theme.js sends JSON to this plain path with the X-CSRF-Token header instead.
put 'tonalmine/appearance', to: 'tonalmine_appearance#update', as: 'tonalmine_appearance', format: false
+14
View File
@@ -0,0 +1,14 @@
# Companion plugin for the Tonalmine theme: an administrator default appearance (set in
# Administration > Settings > Display, below the theme select) and each user's appearance saved
# on their account. The theme works without it; then the choice is stored per browser.
Redmine::Plugin.register :redmine_tonalmine do
name 'Tonalmine'
author 'Andreas Reinhold'
description 'Default appearance and per-user appearance for the Tonalmine theme'
version '0.1.0'
requires_redmine version_or_higher: '7.0.0'
# No :partial: the default is edited on the Display settings tab (see theme.js).
settings default: {}
end
RedmineTonalmine::Hooks # loads and registers the hook listener
@@ -0,0 +1,58 @@
module RedmineTonalmine
# Validates appearance hashes ({mode, contrast, palette, seed, css}) coming from users and from
# the Display settings form. Any part may be missing: it then falls back to the default.
module Appearance
MODES = %w[auto light dark].freeze
CONTRASTS = %w[standard medium high].freeze
# Ids must match PALETTES in tonalmine/javascripts/theme.js (tools/palettes checks this).
PALETTES = %w[purple blue green orange].freeze
KEYS = %w[mode contrast palette seed css].freeze
MAX_CSS = 32.kilobytes
module_function
# Returns a list of problems; empty when the hash is valid.
def errors(hash)
prefs = normalize(hash)
errors = hash.to_h.filter_map do |key, value|
if !KEYS.include?(key.to_s) then "unknown key #{key}"
elsif !value.nil? && !value.is_a?(String) then "invalid #{key}"
end
end
errors << 'invalid mode' if prefs['mode'] && !MODES.include?(prefs['mode'])
errors << 'invalid contrast' if prefs['contrast'] && !CONTRASTS.include?(prefs['contrast'])
errors.concat(custom_errors(prefs))
errors
end
# Returns only the valid parts, with string keys.
def clean(hash)
prefs = normalize(hash)
prefs.delete('mode') unless MODES.include?(prefs['mode'])
prefs.delete('contrast') unless CONTRASTS.include?(prefs['contrast'])
prefs = prefs.except('palette', 'seed', 'css') if custom_errors(prefs).any?
prefs.compact
end
# String keys, known keys only, blank values dropped (the settings form posts "" for unset parts).
def normalize(hash)
hash.to_h.transform_keys(&:to_s).slice(*KEYS).select { |_, value| value.is_a?(String) && value.present? }
end
# palette, seed and css belong together: a custom palette needs its seed and generated CSS.
# A built-in palette may keep the last custom color, so the custom swatch can switch back.
def custom_errors(prefs)
palette, seed, css = prefs.values_at('palette', 'seed', 'css')
return [] if palette.nil? && seed.nil? && css.nil?
required = palette == 'custom'
errors = []
errors << 'invalid palette' unless required || PALETTES.include?(palette)
errors << 'invalid seed' unless (seed.nil? && !required) || seed.to_s.match?(/\A#\h{6}\z/)
# The CSS is rendered into a <script> as JSON and applied via style.textContent; refusing
# "<" keeps it from ever closing either element.
errors << 'invalid css' unless (css.nil? && !required) || (css && !css.include?('<') && css.bytesize <= MAX_CSS)
errors
end
end
end
@@ -0,0 +1,19 @@
module RedmineTonalmine
# Hands the default and the current user's appearance to theme.js. Rendered in <head> right
# after the theme's scripts, so the colors are applied before the page is painted.
class Hooks < Redmine::Hook::ViewListener
def view_layouts_base_html_head(context = {})
defaults = Appearance.clean(Setting.plugin_redmine_tonalmine)
config = {defaults: defaults, user: nil, url: nil}
if User.current.logged?
user = Appearance.clean(User.current.pref[:tonalmine])
# The user's own palette replaces the default one: skip the default's generated CSS.
# Admins keep it: the Display settings tab posts the whole default back.
defaults.delete('css') if user['palette'] && !User.current.admin?
config.update(user: user, url: tonalmine_appearance_path)
end
# to_json escapes <, > and &, so values can't end the script element.
javascript_tag "window.Tonalmine && Tonalmine.configure(#{config.to_json});"
end
end
end
File diff suppressed because one or more lines are too long
+239 -27
View File
@@ -1,84 +1,296 @@
/* /*
* tonalmine: per-user appearance (mode + color palette). * tonalmine: per-user appearance (mode, contrast, color palette).
* *
* Redmine loads this file in <head>, so the saved choice is applied before the page renders * Redmine loads this file in <head>, so the saved choice is applied before the page renders
* (no flash of the default colors). The picker is added to the profile menu and the mobile * (no flash of the default colors). The picker is added to the profile menu and the mobile
* flyout. The choice is stored per browser in localStorage. * flyout. The choice is stored per browser in localStorage. With the redmine_tonalmine plugin,
* the plugin calls Tonalmine.configure() right after this file: it adds the administrator's
* default, and logged-in users' choices are saved on their account instead.
*/ */
(function () { (function () {
// Ids must match PALETTES in tools/palettes/build_palettes.mjs (the build checks this). // Ids must match PALETTES in tools/palettes/build_palettes.mjs and
// redmine_tonalmine/lib/redmine_tonalmine/appearance.rb (the palettes build checks both).
var PALETTES = [ var PALETTES = [
['purple', 'Purple', '#6750A4'], ['purple', 'Purple', '#6750A4'],
['blue', 'Blue', '#0061A4'], ['blue', 'Blue', '#0061A4'],
['teal', 'Teal', '#006A6A'],
['green', 'Green', '#386A20'], ['green', 'Green', '#386A20'],
['orange', 'Orange', '#8B5000'] ['orange', 'Orange', '#8B5000']
]; ];
var MODES = [['auto', 'Auto'], ['light', 'Light'], ['dark', 'Dark']]; var MODES = [['auto', 'Auto'], ['light', 'Light'], ['dark', 'Dark']];
var CONTRASTS = [['standard', 'Standard'], ['medium', 'Medium'], ['high', 'High']];
var KEY = 'tonalmine'; var KEY = 'tonalmine';
var CUSTOM_SEL = ':root[data-palette="custom"]';
var DEFAULT_KEYS = ['mode', 'contrast', 'palette', 'seed', 'css'];
var root = document.documentElement; var root = document.documentElement;
function load() { function load() {
try { return JSON.parse(localStorage.getItem(KEY)) || {}; } catch (e) { return {}; } try { return JSON.parse(localStorage.getItem(KEY)) || {}; } catch (e) { return {}; }
} }
function save(prefs) {
try { localStorage.setItem(KEY, JSON.stringify(prefs)); } catch (e) { /* private mode: applies to this page only */ }
}
function apply(prefs) { function apply(prefs) {
if (prefs.palette && prefs.palette !== PALETTES[0][0]) root.setAttribute('data-palette', prefs.palette); if (prefs.palette && prefs.palette !== PALETTES[0][0]) root.setAttribute('data-palette', prefs.palette);
else root.removeAttribute('data-palette'); else root.removeAttribute('data-palette');
if (prefs.mode === 'light' || prefs.mode === 'dark') root.setAttribute('data-mode', prefs.mode); if (prefs.mode === 'light' || prefs.mode === 'dark') root.setAttribute('data-mode', prefs.mode);
else root.removeAttribute('data-mode'); else root.removeAttribute('data-mode');
if (prefs.contrast === 'medium' || prefs.contrast === 'high') root.setAttribute('data-contrast', prefs.contrast);
else root.removeAttribute('data-contrast');
// The custom palette's rules are generated once (see withScheme) and kept with the prefs.
if (prefs.palette === 'custom' && prefs.css) {
var style = document.getElementById('nm-custom-palette');
if (!style) {
style = document.createElement('style');
style.id = 'nm-custom-palette';
document.head.appendChild(style);
}
style.textContent = prefs.css;
}
} }
var prefs = load(); // The scheme generator (scheme.js, ~90 KB) is only loaded when someone picks a custom color.
apply(prefs); // Redmine serves theme assets under digested names only, so its URL comes from the CSS
// variable --nm-scheme-js (Redmine rewrites url() in CSS to the digested path).
var schemeScript;
function withScheme(done) {
if (window.tonalmineScheme) return done(window.tonalmineScheme);
if (!schemeScript) {
var url = getComputedStyle(root).getPropertyValue('--nm-scheme-js').match(/url\(["']?([^"')]+)/)[1];
schemeScript = document.createElement('script');
schemeScript.src = url;
document.head.appendChild(schemeScript);
}
schemeScript.addEventListener('load', function () { done(window.tonalmineScheme); });
}
// own: the parts this user changed. defaults: the administrator's default (plugin only).
// The page shows the defaults overridden by own, part by part.
var own = load();
var defaults = {};
var url = null; // set when the user's choice is saved on their account (plugin, logged in)
var configured = false;
var draft = null; // the default being edited on the Display settings tab
// palette, seed and css form one part: a custom palette brings its own generated CSS.
function merge(base, top) {
var src = top.palette ? top : base;
return { mode: top.mode || base.mode, contrast: top.contrast || base.contrast,
palette: src.palette, seed: src.seed, css: src.css };
}
function effective() { return merge(defaults, own); }
function isEmpty(prefs) { return !Object.keys(prefs).length; }
function save() {
if (!url) {
try { localStorage.setItem(KEY, JSON.stringify(own)); } catch (e) { /* private mode: applies to this page only */ }
return;
}
var token = document.querySelector('meta[name="csrf-token"]');
fetch(url, {
method: 'PUT',
credentials: 'same-origin',
keepalive: true, // finish even if the user navigates away right after picking
// X-Requested-With: an expired session answers 401 instead of redirecting to the login page.
headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest', 'X-CSRF-Token': token ? token.content : '' },
body: JSON.stringify(own)
}).then(function (response) {
if (response.status !== 204) throw new Error('HTTP ' + response.status);
}).catch(function (error) { console.warn('tonalmine: appearance not saved', error); });
}
// Called by the redmine_tonalmine plugin from <head>, right after this file.
window.Tonalmine = {
configure: function (config) {
defaults = config.defaults || {};
if (config.user) {
url = config.url;
var local = own;
own = config.user;
// A choice made in this browser before the plugin was installed moves to the account once.
if (isEmpty(own) && !isEmpty(local)) {
own = local;
save();
}
// Logged-in choices live on the account only, so the next person on this browser doesn't inherit them.
try { localStorage.removeItem(KEY); } catch (e) { /* nothing stored */ }
}
configured = true;
apply(effective());
}
};
apply(effective());
function button(attr, value, label, pressed, extra) { function button(attr, value, label, pressed, extra) {
return '<button type="button" ' + attr + '="' + value + '" aria-pressed="' + pressed + '"' + (extra || '') + '>' + label + '</button>'; return '<button type="button" ' + attr + '="' + value + '" aria-pressed="' + pressed + '"' + (extra || '') + '>' + label + '</button>';
} }
function pickerHtml() { // isDefault: the administrator's picker on the Display settings tab (no title, no reset).
function pickerHtml(prefs, isDefault) {
var mode = prefs.mode || 'auto'; var mode = prefs.mode || 'auto';
var contrast = prefs.contrast || 'standard';
var palette = prefs.palette || PALETTES[0][0]; var palette = prefs.palette || PALETTES[0][0];
return '<div class="nm-appearance">' + return '<div class="nm-appearance' + (isDefault ? ' nm-appearance--default" data-nm-default' : '"') + '>' +
'<div class="nm-appearance__title">Appearance</div>' + (isDefault ? '' : '<div class="nm-appearance__title">Appearance</div>') +
'<div class="nm-segmented" role="group" aria-label="Mode">' + '<div class="nm-segmented" role="group" aria-label="Mode">' +
MODES.map(function (m) { return button('data-nm-mode', m[0], m[1], m[0] === mode); }).join('') + MODES.map(function (m) { return button('data-nm-mode', m[0], m[1], m[0] === mode); }).join('') +
'</div>' + '</div>' +
'<div class="nm-appearance__title">Contrast</div>' +
'<div class="nm-segmented" role="group" aria-label="Contrast">' +
CONTRASTS.map(function (c) { return button('data-nm-contrast', c[0], c[1], c[0] === contrast); }).join('') +
'</div>' +
'<div class="nm-swatches" role="group" aria-label="Color">' + '<div class="nm-swatches" role="group" aria-label="Color">' +
PALETTES.map(function (p) { PALETTES.map(function (p) {
return button('data-nm-palette', p[0], '', p[0] === palette, return button('data-nm-palette', p[0], '', p[0] === palette,
' class="nm-swatch" title="' + p[1] + '" aria-label="' + p[1] + '" style="--nm-swatch: ' + p[2] + '"'); ' class="nm-swatch" title="' + p[1] + '" aria-label="' + p[1] + '" style="--nm-swatch: ' + p[2] + '"');
}).join('') + }).join('') +
'</div></div>'; '<label class="nm-swatch nm-swatch--custom no-css" data-nm-palette="custom" aria-pressed="' + (palette === 'custom') + '"' +
(prefs.seed ? ' style="--nm-swatch: ' + prefs.seed + '"' : '') + ' title="Custom color">' +
'<input type="color" data-nm-custom value="' + (prefs.seed || PALETTES[0][2].toLowerCase()) + '" aria-label="Custom color"></label>' +
'</div>' +
(isDefault ? '' : '<button type="button" class="nm-appearance__reset" data-nm-reset' + (isEmpty(own) ? ' hidden' : '') + '>Reset to default</button>') +
'</div>';
} }
function sync() { function sync() {
document.querySelectorAll('[data-nm-mode]').forEach(function (b) { document.querySelectorAll('.nm-appearance').forEach(function (picker) {
b.setAttribute('aria-pressed', String(b.getAttribute('data-nm-mode') === (prefs.mode || 'auto'))); var prefs = picker.hasAttribute('data-nm-default') ? draft : effective();
picker.querySelectorAll('[data-nm-mode]').forEach(function (b) {
b.setAttribute('aria-pressed', String(b.getAttribute('data-nm-mode') === (prefs.mode || 'auto')));
});
picker.querySelectorAll('[data-nm-contrast]').forEach(function (b) {
b.setAttribute('aria-pressed', String(b.getAttribute('data-nm-contrast') === (prefs.contrast || 'standard')));
});
picker.querySelectorAll('[data-nm-palette]').forEach(function (b) {
b.setAttribute('aria-pressed', String(b.getAttribute('data-nm-palette') === (prefs.palette || PALETTES[0][0])));
});
if (prefs.seed) {
picker.querySelectorAll('[data-nm-custom]').forEach(function (input) {
input.value = prefs.seed;
input.parentNode.style.setProperty('--nm-swatch', prefs.seed);
});
}
var reset = picker.querySelector('[data-nm-reset]');
if (reset) reset.hidden = isEmpty(own);
}); });
document.querySelectorAll('[data-nm-palette]').forEach(function (b) { document.querySelectorAll('[data-nm-default-input]').forEach(function (input) {
b.setAttribute('aria-pressed', String(b.getAttribute('data-nm-palette') === (prefs.palette || PALETTES[0][0]))); input.value = draft[input.getAttribute('data-nm-default-input')] || '';
}); });
} }
document.addEventListener('click', function (event) { // persist: save the user's choice (the default is saved with the settings form instead).
var target = event.target.closest('[data-nm-mode], [data-nm-palette]'); function changed(isDefault, persist) {
if (!target) return; if (isDefault) apply(draft); // preview
if (target.hasAttribute('data-nm-mode')) prefs.mode = target.getAttribute('data-nm-mode'); else {
else prefs.palette = target.getAttribute('data-nm-palette'); if (persist) save();
save(prefs); apply(effective());
apply(prefs); }
sync(); sync();
}
document.addEventListener('click', function (event) {
var target = event.target.closest('[data-nm-mode], [data-nm-contrast], [data-nm-palette], [data-nm-reset]');
if (!target) return;
var isDefault = !!target.closest('[data-nm-default]');
var prefs = isDefault ? draft : own;
if (target.hasAttribute('data-nm-reset')) own = {};
else if (target.hasAttribute('data-nm-mode')) prefs.mode = target.getAttribute('data-nm-mode');
else if (target.hasAttribute('data-nm-contrast')) prefs.contrast = target.getAttribute('data-nm-contrast');
// The custom swatch opens the color dialog; it only selects the palette once a color was picked.
else if (target.getAttribute('data-nm-palette') !== 'custom' || prefs.css) prefs.palette = target.getAttribute('data-nm-palette');
changed(isDefault, true);
});
document.addEventListener('input', function (event) {
if (!event.target.matches('[data-nm-custom]')) return;
var isDefault = !!event.target.closest('[data-nm-default]');
var seed = event.target.value;
withScheme(function (scheme) {
var prefs = isDefault ? draft : own;
prefs.palette = 'custom';
prefs.seed = seed;
prefs.css = scheme.paletteCss(CUSTOM_SEL, seed);
// While the user drags, only localStorage keeps up; the account gets the final color on change.
changed(isDefault, !url);
});
});
document.addEventListener('change', function (event) {
if (!url || !event.target.matches('[data-nm-custom]') || event.target.closest('[data-nm-default]')) return;
withScheme(save); // queued behind the input event's pending update
});
// Display settings tab (admins, plugin installed): edit the default appearance below the theme
// select. The hidden inputs are saved by the settings form as Setting.plugin_redmine_tonalmine.
function defaultPicker(select) {
draft = merge({}, defaults);
// One labelled form row per group (mode, contrast, color), each in its own default picker
// wrapper so sync() and the click handlers treat it like the whole picker.
var picker = document.createElement('div');
picker.innerHTML = pickerHtml(draft, true);
var groups = picker.querySelectorAll('[role="group"]');
var rows = ['Default appearance', 'Contrast', 'Color'].map(function (label, i) {
var row = document.createElement('p');
row.innerHTML = '<label>' + label + '</label><div class="nm-appearance nm-appearance--default" data-nm-default></div>';
row.lastChild.appendChild(groups[i]);
return row;
});
rows[2].insertAdjacentHTML('beforeend', DEFAULT_KEYS.map(function (key) {
return '<input type="hidden" name="settings[plugin_redmine_tonalmine][' + key + ']" data-nm-default-input="' + key + '">';
}).join(''));
select.closest('p').after(rows[0], rows[1], rows[2]);
// Only shown, and only posted, while Tonalmine is selected; otherwise the saved default stays.
function toggle() {
var on = select.value === 'tonalmine';
rows.forEach(function (row) { row.style.display = on ? '' : 'none'; });
rows[2].querySelectorAll('[data-nm-default-input]').forEach(function (input) { input.disabled = !on; });
}
select.addEventListener('change', toggle);
toggle();
sync();
}
// Calendar/Gantt hover cards: Redmine renders "<a>Tracker #id</a>: subject<br><br>" followed by
// "<strong>Label</strong>: value<br>" lines. Regroup them on first hover into a title and a
// label/value grid (nodes are moved, not re-parsed, so links and avatars stay intact).
function structureTip(tip) {
var lines = [[]];
Array.prototype.slice.call(tip.childNodes).forEach(function (node) {
if (node.nodeName === 'BR') lines.push([]);
else if (node.nodeType !== 3 || node.nodeValue.trim()) lines[lines.length - 1].push(node);
});
var title = document.createElement('span');
var fields = document.createElement('span');
title.className = 'nm-tip__title';
fields.className = 'nm-tip__fields';
lines.filter(function (line) { return line.length; }).forEach(function (line, i) {
var head = line[0];
var value = document.createElement('span');
line.slice(1).forEach(function (node) { value.appendChild(node); });
if (value.firstChild && value.firstChild.nodeType === 3) {
value.firstChild.nodeValue = value.firstChild.nodeValue.replace(/^\s*:\s*/, '');
}
var target = i > 0 && head.nodeName === 'STRONG' ? fields : title;
target.appendChild(head);
target.appendChild(value);
});
tip.replaceChildren(title, fields);
tip.classList.add('nm-tip');
}
document.addEventListener('mouseover', function (event) {
var tip = event.target.closest && event.target.closest('div.tooltip');
tip = tip && tip.querySelector(':scope > span.tip:not(.nm-tip)');
if (tip) structureTip(tip);
}); });
document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function () {
var menu = document.querySelector('#account .dropdown-content .dropdown-divider'); var menu = document.querySelector('#account .dropdown-content .dropdown-divider');
if (menu) menu.insertAdjacentHTML('afterend', pickerHtml() + '<div class="dropdown-divider"></div>'); if (menu) menu.insertAdjacentHTML('afterend', pickerHtml(effective()) + '<div class="dropdown-divider"></div>');
var flyout = document.querySelector('.js-flyout-menu'); var flyout = document.querySelector('.js-flyout-menu');
if (flyout) flyout.insertAdjacentHTML('beforeend', pickerHtml()); if (flyout) flyout.insertAdjacentHTML('beforeend', pickerHtml(effective()));
var select = document.getElementById('settings_ui_theme');
if (select && configured) defaultPicker(select);
}); });
})(); })();
+116 -14
View File
@@ -100,7 +100,7 @@ input.autocomplete {
:where(:root[data-mode="dark"]) input.autocomplete { background-image: url(../images/search-dark.svg); } :where(:root[data-mode="dark"]) input.autocomplete { background-image: url(../images/search-dark.svg); }
@media (prefers-color-scheme: dark) { :where(:root:not([data-mode="light"])) input.autocomplete { background-image: url(../images/search-dark.svg); } } @media (prefers-color-scheme: dark) { :where(:root:not([data-mode="light"])) input.autocomplete { background-image: url(../images/search-dark.svg); } }
select[multiple] { background-image: none; padding-inline-end: 12px; } select[multiple] { background-image: none; padding-inline-end: 12px; }
input::placeholder, textarea::placeholder { color: var(--md-sys-color-on-surface-variant); opacity: 1; } input::placeholder, textarea::placeholder { color: var(--md-sys-color-outline); opacity: 1; } /* lighter than typed text, so an empty field does not look filled */
input[type="checkbox"], input[type="radio"] { accent-color: var(--md-sys-color-primary); } input[type="checkbox"], input[type="radio"] { accent-color: var(--md-sys-color-primary); }
input[type="file"] { color: var(--md-sys-color-on-surface-variant); } input[type="file"] { color: var(--md-sys-color-on-surface-variant); }
input[type="file"]::file-selector-button { input[type="file"]::file-selector-button {
@@ -178,18 +178,36 @@ blockquote { border-inline-start: 4px solid var(--md-sys-color-outline-variant);
/* Core gives wide form fields 90%, 95% or 99% and caps .tabular fields at 95%. They all /* Core gives wide form fields 90%, 95% or 99% and caps .tabular fields at 95%. They all
run to the content edge, so the right edges in a form line up */ run to the content edge, so the right edges in a form line up */
.tabular :is(input, select) { max-inline-size: 100%; } .tabular :is(input, select) { max-inline-size: 100%; }
/* Core pins these to 90% by id */
#user_login, #user_firstname, #user_lastname, #user_mail, #my_account_form select, #user_form select { inline-size: 100%; } #user_login, #user_firstname, #user_lastname, #user_mail, #my_account_form select, #user_form select { inline-size: 100%; }
.tabular textarea, textarea.wiki-edit, textarea.text_cf, input.string_cf, input.link_cf, /* Desktop and tablet landscape: three field widths, so the right edges in a form line up.
textarea#custom_field_possible_values, textarea#custom_field_default_value, Standard: selects and text fields fill the column up to 480px (wider than core's widest,
input#issue_subject, input#document_title, input#content_comments, input#time_entry_comments, size=60). Compact: numbers and dates, 140px fits a date with its picker. Full: long text.
.tabular .jstTabs, .tabular .wiki-preview, .jstBlock .jstTabs, .jstBlock .wiki-preview { inline-size: 100%; box-sizing: border-box; } Below 900px core's responsive.css runs every field full width. */
@media screen and (min-width: 900px) {
.tabular :is(select, input:is([type="text"], [type="password"], [type="email"], [type="url"], [type="search"], [type="tel"], :not([type]))) {
inline-size: 100%;
max-inline-size: 480px;
box-sizing: border-box;
}
.tabular :is(input[type="number"], input[type="date"], input.date, input:is([size="1"], [size="2"], [size="3"], [size="4"], [size="5"], [size="6"])) { inline-size: 140px; }
/* The icon button after a select (new category, new version) stays on its line */
.tabular select:has(+ .icon-only) { inline-size: calc(100% - 36px); max-inline-size: calc(480px - 36px); }
}
html .tabular textarea, html textarea.wiki-edit, html textarea.text_cf, html input.string_cf, html input.link_cf,
html textarea#custom_field_possible_values, html textarea#custom_field_default_value,
html input#issue_subject, html input#document_title, html input#content_comments, html input#time_entry_comments,
html .tabular .jstTabs, html .tabular .wiki-preview, html .jstBlock .jstTabs, html .jstBlock .wiki-preview,
html .tabular.settings :is(textarea, .jstTabs, .wiki-preview) { inline-size: 100%; max-inline-size: none; box-sizing: border-box; }
/* Core pins % Done to 95px by id; it follows the standard width like the other selects */
html select#issue_done_ratio { inline-size: 100%; }
/* An invalid field keeps the error colour while focused */ /* An invalid field keeps the error colour while focused */
.tabular label.error + :is(input, select, textarea):focus { .tabular label.error + :is(input, select, textarea):focus {
border-color: var(--md-sys-color-error); border-color: var(--md-sys-color-error);
outline-color: var(--md-sys-color-error); outline-color: var(--md-sys-color-error);
} }
/* Rows without a field start at the top, like a row with only links (two-factor auth) */ /* Rows without a field start at the top, like a row with only links (two-factor auth) */
.tabular p:not(:has(input:not([type=hidden]), select, textarea)) > label:not(.floating):not(.block):not(.inline):not(.no-css) { padding-block-start: 0; } .tabular p:not(:has(input:not([type=hidden]), select, textarea, .nm-segmented)) > label:not(.floating):not(.block):not(.inline):not(.no-css) { padding-block-start: 0; }
/* Cancel next to a submit button */ /* Cancel next to a submit button */
input[type="submit"] + a { margin-inline-start: 8px; } input[type="submit"] + a { margin-inline-start: 8px; }
@@ -341,20 +359,75 @@ table.progress { border-radius: var(--md-sys-shape-corner-full); overflow: hidde
table.progress td { block-size: 4px; padding: 0; } table.progress td { block-size: 4px; padding: 0; }
.version-overview table.progress td { block-size: 8px; } .version-overview table.progress td { block-size: 8px; }
table.progress td.closed { background: var(--md-sys-color-primary); } table.progress td.closed { background: var(--md-sys-color-primary); }
table.progress td.done { background: color-mix(in srgb, var(--md-sys-color-primary) 55%, var(--md-sys-color-secondary-container)); } table.progress td.done { background: color-mix(in srgb, var(--md-sys-color-primary) 55%, var(--md-sys-color-surface-container-highest)); }
table.progress td.todo { background: var(--md-sys-color-secondary-container); } /* Not secondary-container: at medium/high contrast it is dark and the empty track looks filled */
table.progress td.todo { background: var(--md-sys-color-surface-container-highest); }
/***** Tooltips (plain) *****/ /***** Tooltips (plain) *****/
.ui-tooltip { /* jQuery UI also adds .ui-widget.ui-widget-content, which would win over a bare .ui-tooltip. */
.ui-tooltip.ui-widget.ui-widget-content {
background: var(--md-sys-color-inverse-surface); background: var(--md-sys-color-inverse-surface);
color: var(--md-sys-color-inverse-on-surface); color: var(--md-sys-color-inverse-on-surface);
border: 0;
border-radius: var(--md-sys-shape-corner-extra-small); border-radius: var(--md-sys-shape-corner-extra-small);
box-shadow: none;
font: var(--md-sys-typescale-body-small); font: var(--md-sys-typescale-body-small);
letter-spacing: var(--md-sys-typescale-body-small-tracking); letter-spacing: var(--md-sys-typescale-body-small-tracking);
padding-block: 4px; padding-block: 4px;
padding-inline: 8px; padding-inline: 8px;
} }
/***** Tooltips (rich): calendar and Gantt issue hover cards *****/
div.tooltip:hover span.tip {
inline-size: 300px;
background-color: var(--md-sys-color-surface-container);
color: var(--md-sys-color-on-surface-variant);
border: 0;
border-radius: var(--md-sys-shape-corner-medium);
box-shadow: var(--md-sys-elevation-level2);
padding-block: 12px;
padding-inline: 16px;
font: var(--md-sys-typescale-body-medium);
letter-spacing: var(--md-sys-typescale-body-medium-tracking);
}
div.tooltip span.tip strong { color: var(--md-sys-color-on-surface); font-weight: 500; }
div.tooltip span.tip .avatar { vertical-align: middle; }
/* Structured by theme.js (structureTip): title block, then a label/value grid. */
div.tooltip span.nm-tip .nm-tip__title {
display: flex;
flex-direction: column;
gap: 2px;
margin-block-end: 12px;
}
div.tooltip span.nm-tip .nm-tip__title > a {
font: var(--md-sys-typescale-label-medium);
letter-spacing: var(--md-sys-typescale-label-medium-tracking);
text-decoration: none;
}
div.tooltip span.nm-tip .nm-tip__title > span {
color: var(--md-sys-color-on-surface);
font: var(--md-sys-typescale-title-small);
letter-spacing: var(--md-sys-typescale-title-small-tracking);
}
div.tooltip span.nm-tip .nm-tip__fields {
display: grid;
grid-template-columns: auto 1fr;
gap: 4px 16px;
align-items: center;
}
div.tooltip span.nm-tip .nm-tip__fields > strong {
color: var(--md-sys-color-on-surface-variant);
font: var(--md-sys-typescale-label-medium);
letter-spacing: var(--md-sys-typescale-label-medium-tracking);
}
div.tooltip span.nm-tip .nm-tip__fields > span {
color: var(--md-sys-color-on-surface);
display: flex;
align-items: center;
gap: 6px;
min-inline-size: 0;
}
/***** jQuery UI: dialogs, date picker, menus *****/ /***** jQuery UI: dialogs, date picker, menus *****/
.ui-widget, .ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: var(--md-ref-typeface-plain); } .ui-widget, .ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: var(--md-ref-typeface-plain); }
.ui-widget.ui-widget-content, .ui-widget-content { .ui-widget.ui-widget-content, .ui-widget-content {
@@ -438,7 +511,7 @@ a.icon:hover .icon-svg, a.icon-only:hover .icon-svg, span.icon-actions:hover .ic
.icon-warning svg.icon-svg { color: var(--md-sys-color-warning); } .icon-warning svg.icon-svg { color: var(--md-sys-color-warning); }
div.flash svg.icon-svg, #errorExplanation svg.icon-svg, .conflict svg.icon-svg, div.flash svg.icon-svg, #errorExplanation svg.icon-svg, .conflict svg.icon-svg,
.top-menu__links svg.icon-svg, #account .dropdown-trigger .icon-svg, .top-menu__links svg.icon-svg, #account .dropdown-trigger .icon-svg,
#main-menu svg.icon-svg, #sidebar a.selected svg.icon-svg, .drdn-items svg.icon-svg { color: inherit; } #main-menu svg.icon-svg, #sidebar :is(a.selected, .icon-clear-query) svg.icon-svg, .drdn-items svg.icon-svg { color: inherit; }
html .context-menu-selection svg.icon-svg { color: var(--md-sys-color-on-secondary-container) !important; } html .context-menu-selection svg.icon-svg { color: var(--md-sys-color-on-secondary-container) !important; }
/***** Wiki toolbar (jstoolbar.css loads after the theme) *****/ /***** Wiki toolbar (jstoolbar.css loads after the theme) *****/
@@ -501,12 +574,11 @@ html .table-generator td.selected-cell, html .table-generator td:hover { backgro
letter-spacing: var(--md-sys-typescale-label-medium-tracking); letter-spacing: var(--md-sys-typescale-label-medium-tracking);
} }
/* M3 segmented button */ /* M3 segmented button */
.nm-segmented { display: flex; margin-block-end: 12px; } .nm-segmented { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; margin-block-end: 12px; } /* equal segments */
.nm-segmented button { .nm-segmented button {
flex: 1;
border-radius: 0; border-radius: 0;
block-size: 32px; block-size: 32px;
padding-inline: 12px; padding-inline: 8px;
border: 1px solid var(--md-sys-color-outline); border: 1px solid var(--md-sys-color-outline);
background: transparent; background: transparent;
color: var(--md-sys-color-on-surface); color: var(--md-sys-color-on-surface);
@@ -519,8 +591,9 @@ html .table-generator td.selected-cell, html .table-generator td:hover { backgro
.nm-segmented button:hover { background-image: var(--nm-state-hover); } .nm-segmented button:hover { background-image: var(--nm-state-hover); }
.nm-segmented button[aria-pressed="true"] { background-color: var(--md-sys-color-secondary-container); color: var(--md-sys-color-on-secondary-container); } .nm-segmented button[aria-pressed="true"] { background-color: var(--md-sys-color-secondary-container); color: var(--md-sys-color-on-secondary-container); }
/* Color swatches: the selected one gets a ring */ /* Color swatches: the selected one gets a ring */
.nm-swatches { display: flex; gap: 8px; } .nm-swatches { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 8px; }
.nm-swatch { .nm-swatch {
flex-shrink: 0;
inline-size: 28px; inline-size: 28px;
block-size: 28px; block-size: 28px;
padding: 0; padding: 0;
@@ -529,7 +602,36 @@ html .table-generator td.selected-cell, html .table-generator td:hover { backgro
background: var(--nm-swatch); background: var(--nm-swatch);
cursor: pointer; cursor: pointer;
} }
/* Custom color: a native color input stretched invisibly over a rainbow (or the picked color) swatch */
/* label.: beats core label.no-css { inline-size: auto } (the class keeps .tabular label styles off) */
label.nm-swatch--custom { position: relative; inline-size: 28px; background: var(--nm-swatch, conic-gradient(#e53935, #fdd835, #43a047, #1e88e5, #8e24aa, #e53935)); }
.nm-swatch--custom input { position: absolute; inset: 0; inline-size: 100%; block-size: 100%; padding: 0; border: 0; opacity: 0; cursor: pointer; }
.nm-swatch--custom:focus-within { outline: 2px solid var(--md-sys-color-primary); outline-offset: 2px; }
/* theme.js lazy-loads the scheme generator from here; Redmine rewrites url() to the digested path */
:root { --nm-scheme-js: url(./scheme.js); }
.nm-swatch[aria-pressed="true"] { box-shadow: 0 0 0 2px var(--md-sys-color-surface-container), 0 0 0 4px var(--md-sys-color-on-surface); } .nm-swatch[aria-pressed="true"] { box-shadow: 0 0 0 2px var(--md-sys-color-surface-container), 0 0 0 4px var(--md-sys-color-on-surface); }
/* Reset to default: M3 text button, shown once the user changed something */
.nm-appearance__reset {
margin-block-start: 12px;
block-size: 32px;
padding-inline: 12px;
border: 0;
border-radius: var(--md-sys-shape-corner-full);
background: transparent;
color: var(--md-sys-color-primary);
font: var(--md-sys-typescale-label-large);
cursor: pointer;
}
.nm-appearance__reset:hover { background-image: var(--nm-state-hover); }
/* Default appearance on Administration > Settings > Display (redmine_tonalmine plugin) */
.nm-appearance--default { max-inline-size: 280px; padding: 0; }
/* Each group is its own form row: 40px tall like the fields (M3 segmented button height), so
the label lines up; the row padding spaces them */
.nm-appearance--default .nm-segmented { margin: 0; }
.nm-appearance--default .nm-segmented button { block-size: 40px; margin-block: 0; }
.nm-appearance--default .nm-swatches { min-block-size: 40px; align-items: center; }
/* The custom swatch is a <label class="no-css">; also undo .tabular.settings label margins and mobile full width */
html .tabular label.nm-swatch--custom { display: block; margin: 0; inline-size: 28px; }
html .flyout-menu .nm-appearance { padding-inline: 16px; } html .flyout-menu .nm-appearance { padding-inline: 16px; }
html .flyout-menu .nm-appearance__title { color: var(--md-sys-color-on-surface-variant); } html .flyout-menu .nm-appearance__title { color: var(--md-sys-color-on-surface-variant); }
+25 -9
View File
@@ -202,7 +202,17 @@ html .dropdown-content .dropdown-items a:hover,
} }
#main-menu .menu-children li a:hover::after { display: none; } #main-menu .menu-children li a:hover::after { display: none; }
.drdn-items > .selected { background-color: var(--md-sys-color-secondary-container); color: var(--md-sys-color-on-secondary-container); } .drdn-items > .selected { background-color: var(--md-sys-color-secondary-container); color: var(--md-sys-color-on-secondary-container); }
/* At medium/high contrast secondary-container turns into a dark block; the check mark already marks the item */
:root:is([data-contrast="medium"], [data-contrast="high"]) .drdn-items > .selected { background-color: var(--md-sys-color-surface-container-highest); color: var(--md-sys-color-on-surface); }
#project-jump .drdn-items > a:hover { background-color: transparent; color: var(--md-sys-color-on-surface) !important; } #project-jump .drdn-items > a:hover { background-color: transparent; color: var(--md-sys-color-on-surface) !important; }
/* Section headers (Bookmarks, Recently used, All Projects): small primary labels, a divider between sections */
#project-jump .drdn-items > strong {
padding-block: 12px 4px;
color: var(--md-sys-color-primary) !important; /* core: gray !important */
font: var(--md-sys-typescale-label-medium);
letter-spacing: var(--md-sys-typescale-label-medium-tracking);
}
#project-jump .drdn-items > strong:not(:first-child) { margin-block-start: 8px; border-block-start: 1px solid var(--md-sys-color-outline-variant); }
div + .drdn-items, .dropdown-divider, #account .dropdown-content .logout { border-color: var(--md-sys-color-outline-variant); } div + .drdn-items, .dropdown-divider, #account .dropdown-content .logout { border-color: var(--md-sys-color-outline-variant); }
#account .dropdown-content .user-info { color: var(--md-sys-color-on-surface-variant); font: var(--md-sys-typescale-body-medium); } #account .dropdown-content .user-info { color: var(--md-sys-color-on-surface-variant); font: var(--md-sys-typescale-body-medium); }
#account .dropdown-content .user-info .user-name { color: var(--md-sys-color-on-surface); font: var(--md-sys-typescale-title-small); } #account .dropdown-content .user-info .user-name { color: var(--md-sys-color-on-surface); font: var(--md-sys-typescale-title-small); }
@@ -288,28 +298,34 @@ div + .drdn-items, .dropdown-divider, #account .dropdown-content .logout { borde
#sidebar ul + ul, #sidebar ul + p { margin-block-start: 12px; } #sidebar ul + ul, #sidebar ul + p { margin-block-start: 12px; }
/* Navigation-drawer style items for plain link lists in the sidebar (queries, sort order, /* Navigation-drawer style items for plain link lists in the sidebar (queries, sort order,
versions, wiki index, …): a link that is alone in its list item */ versions, wiki index, …): a link that is alone in its list item, or a saved query */
#sidebar li > a:only-child, #admin-menu li a { #sidebar li > a:is(:only-child, .query), #admin-menu li a {
display: flex; display: flex;
align-items: center; align-items: center;
min-block-size: 36px; min-block-size: 36px;
padding-block: 8px; /* long names wrap: keep the items apart */
padding-inline: 12px; padding-inline: 12px;
box-sizing: border-box;
border-radius: var(--md-sys-shape-corner-full); border-radius: var(--md-sys-shape-corner-full);
color: var(--md-sys-color-on-surface-variant); color: var(--md-sys-color-on-surface-variant);
font: var(--md-sys-typescale-label-large); font: var(--md-sys-typescale-body-medium); /* regular weight: only the section titles are bold */
letter-spacing: var(--md-sys-typescale-label-large-tracking); letter-spacing: var(--md-sys-typescale-body-medium-tracking);
transition: var(--nm-transition); transition: var(--nm-transition);
} }
#sidebar li > a:only-child:hover, #admin-menu li a:hover { background-image: var(--nm-state-hover); text-decoration: none; } #sidebar li > a:is(:only-child, .query):hover, #admin-menu li a:hover { background-image: var(--nm-state-hover); text-decoration: none; }
#sidebar li > a.selected:only-child, #admin-menu li a.selected { #sidebar li > a.selected:is(:only-child, .query), #admin-menu li a.selected {
margin-inline-start: 0; /* core: -6px, inset marker bar */ margin-inline-start: 0; /* core: -6px, inset marker bar */
padding-block: 0; padding-block: 8px;
box-shadow: none; box-shadow: none;
line-height: normal;
background-color: var(--md-sys-color-secondary-container); background-color: var(--md-sys-color-secondary-container);
color: var(--md-sys-color-on-secondary-container); color: var(--md-sys-color-on-secondary-container);
} }
#admin-menu li { padding-block-end: 2px; } #admin-menu li, #sidebar li:has(> a:is(:only-child, .query)) { padding-block-end: 2px; }
/* The selected query also holds its "clear" icon: it sits at the end, inside the pill */
#sidebar li:has(> .icon-clear-query) { display: grid; align-items: center; }
#sidebar li:has(> .icon-clear-query) > * { grid-area: 1 / 1; }
#sidebar li:has(> .icon-clear-query) > a.query { padding-inline-end: 40px; }
#sidebar li > .icon-clear-query { justify-self: end; margin-inline-end: 12px; color: var(--md-sys-color-on-secondary-container); }
/***** Footer *****/ /***** Footer *****/
#footer { #footer {
+114 -2
View File
@@ -46,13 +46,38 @@
mask: url(../images/menu.svg) center / 24px no-repeat; mask: url(../images/menu.svg) center / 24px no-repeat;
} }
/* Flyout: navigation drawer */ /* Flyout: M3 modal navigation drawer. It slides over the page behind a scrim instead of
core's push, which shoves page and header off screen. Tapping the scrim hits #main,
which core's responsive.js already binds to close the flyout. */
html.flyout-is-active :is(#wrapper, #header) { inset-inline-end: 0; }
html .flyout-menu { html .flyout-menu {
--nm-drawer-width: min(360px, calc(100vw - 56px)); /* M3 modal drawer; core's 250px squeezes the appearance picker */
position: fixed;
inset-block: 0;
inset-inline-end: calc(-1 * var(--nm-drawer-width));
inline-size: var(--nm-drawer-width);
z-index: 10001; /* over core's fixed header (9999) and the scrim */
block-size: auto;
overflow-y: auto;
overscroll-behavior: contain;
visibility: hidden;
transition: inset-inline-end 250ms cubic-bezier(0.2, 0, 0, 1), visibility 250ms;
border-start-start-radius: var(--md-sys-shape-corner-large);
border-end-start-radius: var(--md-sys-shape-corner-large);
box-shadow: var(--md-sys-elevation-level1);
background-color: var(--md-sys-color-surface-container-low); background-color: var(--md-sys-color-surface-container-low);
color: var(--md-sys-color-on-surface); color: var(--md-sys-color-on-surface);
padding-inline: 12px; padding-inline: 12px;
box-sizing: border-box; box-sizing: border-box;
} }
html.flyout-is-active .flyout-menu { inset-inline-end: 0; visibility: visible; }
html.flyout-is-active #main::after {
content: '';
position: fixed;
inset: 0;
z-index: 10000;
background-color: color-mix(in srgb, var(--md-sys-color-scrim) 32%, transparent);
}
html .flyout-menu h3 { html .flyout-menu h3 {
block-size: auto; block-size: auto;
margin-block: 16px 4px; margin-block: 16px 4px;
@@ -64,6 +89,26 @@
letter-spacing: var(--md-sys-typescale-title-small-tracking); letter-spacing: var(--md-sys-typescale-title-small-tracking);
text-transform: none; text-transform: none;
} }
/* M3 drawer dividers between groups; headings alone look like one more nav item */
html .flyout-menu > :is(h3, .flyout-menu__sidebar:has(#sidebar-wrapper > *), .nm-appearance) {
display: block; /* the sidebar group is a span */
margin-block-start: 8px;
padding-block-start: 8px;
border-block-start: 1px solid var(--md-sys-color-outline-variant);
}
html .flyout-menu > h3 { padding-block-start: 16px; }
/* Sidebar forms (e.g. activity filters) line up with the nav labels; a field stacks under
its label, which is a bare text node, so grid is the only way to put it on its own row */
html .flyout-menu__sidebar :is(form > p, li:has(> input[type="checkbox"])) { padding-inline: 16px; }
html .flyout-menu__sidebar p:has(> :is(select, input:not([type="submit"], [type="checkbox"], [type="radio"]))) {
display: grid;
gap: 4px;
color: var(--md-sys-color-on-surface-variant);
font: var(--md-sys-typescale-label-large);
}
html .flyout-menu__sidebar p > :is(select, input:not([type="submit"], [type="checkbox"], [type="radio"])) { inline-size: 100%; box-sizing: border-box; }
html .flyout-menu__sidebar li:has(> input[type="checkbox"]) { display: flex; align-items: center; gap: 12px; min-block-size: 40px; border: 0; }
html .flyout-menu .flyout-menu__sidebar li:has(> input[type="checkbox"]) a { block-size: auto; padding: 0; line-height: inherit; }
html .flyout-menu h4 { color: var(--md-sys-color-on-surface); } html .flyout-menu h4 { color: var(--md-sys-color-on-surface); }
html .flyout-menu a { color: var(--md-sys-color-on-surface-variant); } html .flyout-menu a { color: var(--md-sys-color-on-surface-variant); }
html .flyout-menu ul li a, html .flyout-menu ul li:first-child a { html .flyout-menu ul li a, html .flyout-menu ul li:first-child a {
@@ -96,7 +141,59 @@
html .search-magnifier--flyout { inset-inline-start: 28px; } html .search-magnifier--flyout { inset-inline-start: 28px; }
html .flyout-menu .flyout-menu__avatar { padding-inline-start: 16px; } html .flyout-menu .flyout-menu__avatar { padding-inline-start: 16px; }
html .cal .week-number { background-color: var(--md-sys-color-surface-container); border-color: var(--md-sys-color-outline-variant); } /* Calendar month bar: the month select fills the row, Apply / Clear / Save wrap below */
html body.controller-calendars p.buttons select#month { flex: 1 1 0; min-inline-size: 0; }
html body.controller-calendars #query_form_with_buttons::after { content: ''; order: 2; flex-basis: 100%; margin-block: -4px; } /* line break */
html body.controller-calendars p.buttons a { order: 3; }
/* Calendar as an agenda: weekday column on the left, issues on the right. Days of the
neighbouring months are dropped; empty days shrink to one line. */
html .cal .week-number {
padding-block: 8px;
padding-inline: 16px;
border: 0;
border-block-start: 1px solid var(--md-sys-color-outline-variant);
background-color: var(--md-sys-color-surface-container);
color: var(--md-sys-color-on-surface-variant);
font: var(--md-sys-typescale-label-medium);
letter-spacing: var(--md-sys-typescale-label-medium-tracking);
}
html .cal .week-number:first-child { border-block-start: 0; }
html .cal .calbody.other-month { display: none; }
html .cal .calbody {
display: grid;
grid-template-columns: 40px minmax(0, 1fr);
column-gap: 8px;
align-items: start;
min-block-size: 0;
padding-block: 8px;
padding-inline: 8px;
border: 0;
border-block-start: 1px solid var(--md-sys-color-outline-variant);
}
/* ponytail: the date spans 50 implicit rows (empty ones collapse to 0); a day with more
issues lets the extra ones fall under the date */
html .cal .calbody p.day-num {
grid-row: 1 / span 50;
display: flex;
flex-direction: column;
align-items: center;
margin: 0;
text-align: center;
}
html .cal .calbody .day-value { inline-size: 32px; block-size: 32px; font: var(--md-sys-typescale-title-medium); }
html .cal .calbody .abbr-day { color: var(--md-sys-color-on-surface-variant); font: var(--md-sys-typescale-label-small); }
html .cal .calbody div.issue, html .cal .calbody > .icon { grid-column: 2; margin-block: 0 4px; }
/* An empty day is one short line: date and weekday side by side, greyed out */
html .cal .calbody:not(:has(div.issue, > .icon)) { padding-block: 4px; }
html .cal .calbody:not(:has(div.issue, > .icon)) p.day-num { flex-direction: row; gap: 8px; color: var(--md-sys-color-on-surface-variant); }
html .cal .calbody:not(:has(div.issue, > .icon)) .day-value { flex: none; inline-size: 40px; block-size: 24px; font: var(--md-sys-typescale-label-large); }
/* Today: tinted row with a primary stripe on its leading edge */
html .cal .calbody.today {
border-inline-start: 4px solid var(--md-sys-color-primary);
padding-inline-start: 4px;
background-color: color-mix(in srgb, var(--md-sys-color-primary) 8%, var(--md-sys-color-surface));
}
/* Page actions become M3 outlined buttons, like Apply / Clear. The forum topic wraps its /* Page actions become M3 outlined buttons, like Apply / Clear. The forum topic wraps its
actions in a quote-reply controller, so core's #content > .contextual misses them. */ actions in a quote-reply controller, so core's #content > .contextual misses them. */
html #content > [data-controller="quote-reply"] > .contextual { html #content > [data-controller="quote-reply"] > .contextual {
@@ -132,8 +229,23 @@
/* Form fields run full width; a select keeps room for the icon button after it */ /* Form fields run full width; a select keeps room for the icon button after it */
html form .attributes select { inline-size: 100%; } html form .attributes select { inline-size: 100%; }
html form .attributes select:has(+ .icon-only) { inline-size: calc(100% - 36px); } html form .attributes select:has(+ .icon-only) { inline-size: calc(100% - 36px); }
/* Core leaves the tracker at 90%; only the project row shares its line (with "Private") */
html #all_attributes #issue_tracker_id { inline-size: 100%; }
/* Core leaves dates at their content width; every field runs full width here */
html .tabular input:is(.date, [type="date"]) { inline-size: 100%; max-inline-size: 100%; }
html .tabular input[type="radio"] { inline-size: auto; } html .tabular input[type="radio"] { inline-size: auto; }
/* Query filters: operator and values run full width under the checkbox label instead of
the desktop's fixed 180px / 260px columns. A flex-basis of 0 overrides those widths, so
"between" fields split the row and a multi-select toggle keeps its spot after the field. */
html #filters-table .filter { row-gap: 8px; margin-block-end: 8px; }
html #filters-table .filter :is(.operator, .values) { flex: 1 1 100%; }
html #filters-table .filter .values:not(:has(> span:not([style*="none"]))) { display: none; } /* e.g. status "open" */
html #filters-table .filter .operator select { inline-size: 100%; }
html #filters-table .filter .values > span { display: flex; flex: 1 1 0; align-items: center; gap: 8px; }
html #filters-table .filter .values .value:not([size="3"]) { flex: 1 1 0; min-inline-size: 0; }
html .add-filter { justify-content: stretch; margin-block-start: 16px; } /* full-width button */
/* Wiki toolbar: buttons wrap instead of being cut off */ /* Wiki toolbar: buttons wrap instead of being cut off */
html #content .jstTabs.tabs { clear: both; block-size: auto; overflow: visible; } /* below the floated field label */ html #content .jstTabs.tabs { clear: both; block-size: auto; overflow: visible; } /* below the floated field label */
html #content .jstTabs.tabs ul { position: static; display: flex; flex-wrap: wrap; inline-size: auto; min-inline-size: 0; } html #content .jstTabs.tabs ul { position: static; display: flex; flex-wrap: wrap; inline-size: auto; min-inline-size: 0; }
+34 -2
View File
@@ -122,7 +122,7 @@ div.wiki *:not(pre) > code, div.wiki > code {
.syntaxhl :is(.nv, .vc, .vg, .vi, .vm, .nl, .ni) { color: var(--md-sys-color-warning); font-weight: normal; } .syntaxhl :is(.nv, .vc, .vg, .vi, .vm, .nl, .ni) { color: var(--md-sys-color-warning); font-weight: normal; }
.syntaxhl :is(.err, .gd, .gr, .ne, .gt) { color: var(--md-sys-color-error); background-color: transparent; } .syntaxhl :is(.err, .gd, .gr, .ne, .gt) { color: var(--md-sys-color-error); background-color: transparent; }
.syntaxhl :is(.o) { color: var(--md-sys-color-on-surface); } .syntaxhl :is(.o) { color: var(--md-sys-color-on-surface); }
.syntaxhl .hll { background-color: var(--md-sys-color-secondary-container); } .syntaxhl .hll { background-color: var(--md-sys-color-surface-container-highest); } /* stays a surface tone at every contrast, so token colors stay readable */
/***** Activity *****/ /***** Activity *****/
/* One card per day (the <dl>), entries are M3 list rows. Each entry is a <dt> + <dd> pair: /* One card per day (the <dl>), entries are M3 list rows. Each entry is a <dt> + <dd> pair:
@@ -342,7 +342,7 @@ div#roadmap .version-article {
border: 1px solid var(--md-sys-color-outline-variant); border: 1px solid var(--md-sys-color-outline-variant);
border-radius: var(--md-sys-shape-corner-medium); border-radius: var(--md-sys-shape-corner-medium);
} }
div#roadmap header:target { background-color: var(--md-sys-color-secondary-container); } div#roadmap header:target { background-color: var(--md-sys-color-surface-container-highest); }
div#version-summary { background-color: transparent; } div#version-summary { background-color: transparent; }
/* Overview and issue table share one left edge (core indents the overview by 1.5rem) */ /* Overview and issue table share one left edge (core indents the overview by 1.5rem) */
@@ -374,6 +374,38 @@ ul.cal {
.cal .calbody.other-month, .cal .calbody.nwday:not(.other-month) { background-color: var(--md-sys-color-surface-container-low); } .cal .calbody.other-month, .cal .calbody.nwday:not(.other-month) { background-color: var(--md-sys-color-surface-container-low); }
.cal .calbody.today .day-value { color: var(--md-sys-color-on-primary); } .cal .calbody.today .day-value { color: var(--md-sys-color-on-primary); }
/* Month bar: ‹ [month] [year] › then Apply / Clear / Save. The arrows and the selects live
in separate containers, so those dissolve into one flex row. */
body.controller-calendars #query_form_with_buttons { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-block-end: 16px; } /* p.buttons had it */
body.controller-calendars #query_form_content { flex: 1 1 100%; }
body.controller-calendars :is(#query_form_with_buttons > .pagination, #query_form_with_buttons > .pagination ul, #query_form_with_buttons > p.buttons) { display: contents; }
body.controller-calendars p.buttons label { position: absolute; clip-path: inset(50%); inline-size: 1px; block-size: 1px; overflow: hidden; }
body.controller-calendars .pagination li { margin: 0; }
body.controller-calendars .pagination li.next { order: 1; }
body.controller-calendars p.buttons a { order: 2; }
/* Previous / next month: icon buttons; the month name stays as the accessible text */
body.controller-calendars .pagination li a {
display: block;
inline-size: 40px;
block-size: 40px;
padding: 0;
border: 0;
border-radius: var(--md-sys-shape-corner-full);
font-size: 0;
color: var(--md-sys-color-on-surface-variant);
}
body.controller-calendars .pagination li a:hover { background-image: var(--nm-state-hover); }
body.controller-calendars .pagination li a::before {
content: '';
display: block;
block-size: 100%;
background-color: currentColor;
mask: url(../images/chevron-left-idnt.svg) center / 24px no-repeat;
}
body.controller-calendars .pagination li.next a::before,
html[dir="rtl"] body.controller-calendars .pagination li.previous a::before { transform: scaleX(-1); }
html[dir="rtl"] body.controller-calendars .pagination li.next a::before { transform: none; }
/***** Gantt (gantt.css loads after the theme, hence the `html` prefix) *****/ /***** Gantt (gantt.css loads after the theme, hence the `html` prefix) *****/
/* /*
* Geometry is set inline by Redmine: rows every 20px, each with a 16px text line whose top is * Geometry is set inline by Redmine: rows every 20px, each with a 16px text line whose top is
+26 -6
View File
@@ -1,8 +1,9 @@
/* /*
* Generated by tools/palettes/build_palettes.mjs; do not edit by hand. * Generated by tools/palettes/build_palettes.mjs; do not edit by hand.
* M3 color roles (TonalSpot, contrast 0) per palette, light and dark. * M3 color roles (TonalSpot) per palette, light and dark, at contrast 0, 0.5 and 1.
* <html data-palette="..."> picks the palette, <html data-mode="light|dark"> forces a mode; * <html data-palette="..."> picks the palette, <html data-mode="light|dark"> forces a mode;
* without data-mode the OS setting (prefers-color-scheme) decides. * without data-mode the OS setting (prefers-color-scheme) decides.
* <html data-contrast="medium|high"> picks the contrast level; without it, standard.
*/ */
:root { color-scheme: light; } :root { color-scheme: light; }
@@ -13,23 +14,42 @@
:root { --md-sys-color-primary: #65558f; --md-sys-color-on-primary: #ffffff; --md-sys-color-primary-container: #e9ddff; --md-sys-color-on-primary-container: #4d3d75; --md-sys-color-inverse-primary: #cfbdfe; --md-sys-color-secondary: #625b71; --md-sys-color-on-secondary: #ffffff; --md-sys-color-secondary-container: #e8def8; --md-sys-color-on-secondary-container: #4a4458; --md-sys-color-tertiary: #7e5260; --md-sys-color-on-tertiary: #ffffff; --md-sys-color-tertiary-container: #ffd9e3; --md-sys-color-on-tertiary-container: #633b48; --md-sys-color-error: #ba1a1a; --md-sys-color-on-error: #ffffff; --md-sys-color-error-container: #ffdad6; --md-sys-color-on-error-container: #93000a; --md-sys-color-background: #fdf7ff; --md-sys-color-on-background: #1d1b20; --md-sys-color-surface: #fdf7ff; --md-sys-color-surface-dim: #ded8e0; --md-sys-color-surface-bright: #fdf7ff; --md-sys-color-surface-container-lowest: #ffffff; --md-sys-color-surface-container-low: #f8f2fa; --md-sys-color-surface-container: #f2ecf4; --md-sys-color-surface-container-high: #ece6ee; --md-sys-color-surface-container-highest: #e6e0e9; --md-sys-color-on-surface: #1d1b20; --md-sys-color-surface-variant: #e7e0eb; --md-sys-color-on-surface-variant: #49454e; --md-sys-color-inverse-surface: #322f35; --md-sys-color-inverse-on-surface: #f5eff7; --md-sys-color-outline: #7a757f; --md-sys-color-outline-variant: #cac4cf; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #65558f; --md-sys-color-success: #0a6d36; --md-sys-color-on-success: #ffffff; --md-sys-color-success-container: #9ef6b0; --md-sys-color-on-success-container: #00210b; --md-sys-color-warning: #885200; --md-sys-color-on-warning: #ffffff; --md-sys-color-warning-container: #ffddbb; --md-sys-color-on-warning-container: #2b1700; } :root { --md-sys-color-primary: #65558f; --md-sys-color-on-primary: #ffffff; --md-sys-color-primary-container: #e9ddff; --md-sys-color-on-primary-container: #4d3d75; --md-sys-color-inverse-primary: #cfbdfe; --md-sys-color-secondary: #625b71; --md-sys-color-on-secondary: #ffffff; --md-sys-color-secondary-container: #e8def8; --md-sys-color-on-secondary-container: #4a4458; --md-sys-color-tertiary: #7e5260; --md-sys-color-on-tertiary: #ffffff; --md-sys-color-tertiary-container: #ffd9e3; --md-sys-color-on-tertiary-container: #633b48; --md-sys-color-error: #ba1a1a; --md-sys-color-on-error: #ffffff; --md-sys-color-error-container: #ffdad6; --md-sys-color-on-error-container: #93000a; --md-sys-color-background: #fdf7ff; --md-sys-color-on-background: #1d1b20; --md-sys-color-surface: #fdf7ff; --md-sys-color-surface-dim: #ded8e0; --md-sys-color-surface-bright: #fdf7ff; --md-sys-color-surface-container-lowest: #ffffff; --md-sys-color-surface-container-low: #f8f2fa; --md-sys-color-surface-container: #f2ecf4; --md-sys-color-surface-container-high: #ece6ee; --md-sys-color-surface-container-highest: #e6e0e9; --md-sys-color-on-surface: #1d1b20; --md-sys-color-surface-variant: #e7e0eb; --md-sys-color-on-surface-variant: #49454e; --md-sys-color-inverse-surface: #322f35; --md-sys-color-inverse-on-surface: #f5eff7; --md-sys-color-outline: #7a757f; --md-sys-color-outline-variant: #cac4cf; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #65558f; --md-sys-color-success: #0a6d36; --md-sys-color-on-success: #ffffff; --md-sys-color-success-container: #9ef6b0; --md-sys-color-on-success-container: #00210b; --md-sys-color-warning: #885200; --md-sys-color-on-warning: #ffffff; --md-sys-color-warning-container: #ffddbb; --md-sys-color-on-warning-container: #2b1700; }
:root[data-mode="dark"] { --md-sys-color-primary: #cfbdfe; --md-sys-color-on-primary: #36275d; --md-sys-color-primary-container: #4d3d75; --md-sys-color-on-primary-container: #e9ddff; --md-sys-color-inverse-primary: #65558f; --md-sys-color-secondary: #cbc2db; --md-sys-color-on-secondary: #332d41; --md-sys-color-secondary-container: #4a4458; --md-sys-color-on-secondary-container: #e8def8; --md-sys-color-tertiary: #efb8c8; --md-sys-color-on-tertiary: #4a2532; --md-sys-color-tertiary-container: #633b48; --md-sys-color-on-tertiary-container: #ffd9e3; --md-sys-color-error: #ffb4ab; --md-sys-color-on-error: #690005; --md-sys-color-error-container: #93000a; --md-sys-color-on-error-container: #ffdad6; --md-sys-color-background: #141218; --md-sys-color-on-background: #e6e0e9; --md-sys-color-surface: #141218; --md-sys-color-surface-dim: #141218; --md-sys-color-surface-bright: #3b383e; --md-sys-color-surface-container-lowest: #0f0d13; --md-sys-color-surface-container-low: #1d1b20; --md-sys-color-surface-container: #211f24; --md-sys-color-surface-container-high: #2b292f; --md-sys-color-surface-container-highest: #36343a; --md-sys-color-on-surface: #e6e0e9; --md-sys-color-surface-variant: #49454e; --md-sys-color-on-surface-variant: #cac4cf; --md-sys-color-inverse-surface: #e6e0e9; --md-sys-color-inverse-on-surface: #322f35; --md-sys-color-outline: #948f99; --md-sys-color-outline-variant: #49454e; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #cfbdfe; --md-sys-color-success: #82d996; --md-sys-color-on-success: #003918; --md-sys-color-success-container: #005226; --md-sys-color-on-success-container: #9ef6b0; --md-sys-color-warning: #ffb869; --md-sys-color-on-warning: #482900; --md-sys-color-warning-container: #673d00; --md-sys-color-on-warning-container: #ffddbb; } :root[data-mode="dark"] { --md-sys-color-primary: #cfbdfe; --md-sys-color-on-primary: #36275d; --md-sys-color-primary-container: #4d3d75; --md-sys-color-on-primary-container: #e9ddff; --md-sys-color-inverse-primary: #65558f; --md-sys-color-secondary: #cbc2db; --md-sys-color-on-secondary: #332d41; --md-sys-color-secondary-container: #4a4458; --md-sys-color-on-secondary-container: #e8def8; --md-sys-color-tertiary: #efb8c8; --md-sys-color-on-tertiary: #4a2532; --md-sys-color-tertiary-container: #633b48; --md-sys-color-on-tertiary-container: #ffd9e3; --md-sys-color-error: #ffb4ab; --md-sys-color-on-error: #690005; --md-sys-color-error-container: #93000a; --md-sys-color-on-error-container: #ffdad6; --md-sys-color-background: #141218; --md-sys-color-on-background: #e6e0e9; --md-sys-color-surface: #141218; --md-sys-color-surface-dim: #141218; --md-sys-color-surface-bright: #3b383e; --md-sys-color-surface-container-lowest: #0f0d13; --md-sys-color-surface-container-low: #1d1b20; --md-sys-color-surface-container: #211f24; --md-sys-color-surface-container-high: #2b292f; --md-sys-color-surface-container-highest: #36343a; --md-sys-color-on-surface: #e6e0e9; --md-sys-color-surface-variant: #49454e; --md-sys-color-on-surface-variant: #cac4cf; --md-sys-color-inverse-surface: #e6e0e9; --md-sys-color-inverse-on-surface: #322f35; --md-sys-color-outline: #948f99; --md-sys-color-outline-variant: #49454e; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #cfbdfe; --md-sys-color-success: #82d996; --md-sys-color-on-success: #003918; --md-sys-color-success-container: #005226; --md-sys-color-on-success-container: #9ef6b0; --md-sys-color-warning: #ffb869; --md-sys-color-on-warning: #482900; --md-sys-color-warning-container: #673d00; --md-sys-color-on-warning-container: #ffddbb; }
@media (prefers-color-scheme: dark) { :root:not([data-mode="light"]) { --md-sys-color-primary: #cfbdfe; --md-sys-color-on-primary: #36275d; --md-sys-color-primary-container: #4d3d75; --md-sys-color-on-primary-container: #e9ddff; --md-sys-color-inverse-primary: #65558f; --md-sys-color-secondary: #cbc2db; --md-sys-color-on-secondary: #332d41; --md-sys-color-secondary-container: #4a4458; --md-sys-color-on-secondary-container: #e8def8; --md-sys-color-tertiary: #efb8c8; --md-sys-color-on-tertiary: #4a2532; --md-sys-color-tertiary-container: #633b48; --md-sys-color-on-tertiary-container: #ffd9e3; --md-sys-color-error: #ffb4ab; --md-sys-color-on-error: #690005; --md-sys-color-error-container: #93000a; --md-sys-color-on-error-container: #ffdad6; --md-sys-color-background: #141218; --md-sys-color-on-background: #e6e0e9; --md-sys-color-surface: #141218; --md-sys-color-surface-dim: #141218; --md-sys-color-surface-bright: #3b383e; --md-sys-color-surface-container-lowest: #0f0d13; --md-sys-color-surface-container-low: #1d1b20; --md-sys-color-surface-container: #211f24; --md-sys-color-surface-container-high: #2b292f; --md-sys-color-surface-container-highest: #36343a; --md-sys-color-on-surface: #e6e0e9; --md-sys-color-surface-variant: #49454e; --md-sys-color-on-surface-variant: #cac4cf; --md-sys-color-inverse-surface: #e6e0e9; --md-sys-color-inverse-on-surface: #322f35; --md-sys-color-outline: #948f99; --md-sys-color-outline-variant: #49454e; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #cfbdfe; --md-sys-color-success: #82d996; --md-sys-color-on-success: #003918; --md-sys-color-success-container: #005226; --md-sys-color-on-success-container: #9ef6b0; --md-sys-color-warning: #ffb869; --md-sys-color-on-warning: #482900; --md-sys-color-warning-container: #673d00; --md-sys-color-on-warning-container: #ffddbb; } } @media (prefers-color-scheme: dark) { :root:not([data-mode="light"]) { --md-sys-color-primary: #cfbdfe; --md-sys-color-on-primary: #36275d; --md-sys-color-primary-container: #4d3d75; --md-sys-color-on-primary-container: #e9ddff; --md-sys-color-inverse-primary: #65558f; --md-sys-color-secondary: #cbc2db; --md-sys-color-on-secondary: #332d41; --md-sys-color-secondary-container: #4a4458; --md-sys-color-on-secondary-container: #e8def8; --md-sys-color-tertiary: #efb8c8; --md-sys-color-on-tertiary: #4a2532; --md-sys-color-tertiary-container: #633b48; --md-sys-color-on-tertiary-container: #ffd9e3; --md-sys-color-error: #ffb4ab; --md-sys-color-on-error: #690005; --md-sys-color-error-container: #93000a; --md-sys-color-on-error-container: #ffdad6; --md-sys-color-background: #141218; --md-sys-color-on-background: #e6e0e9; --md-sys-color-surface: #141218; --md-sys-color-surface-dim: #141218; --md-sys-color-surface-bright: #3b383e; --md-sys-color-surface-container-lowest: #0f0d13; --md-sys-color-surface-container-low: #1d1b20; --md-sys-color-surface-container: #211f24; --md-sys-color-surface-container-high: #2b292f; --md-sys-color-surface-container-highest: #36343a; --md-sys-color-on-surface: #e6e0e9; --md-sys-color-surface-variant: #49454e; --md-sys-color-on-surface-variant: #cac4cf; --md-sys-color-inverse-surface: #e6e0e9; --md-sys-color-inverse-on-surface: #322f35; --md-sys-color-outline: #948f99; --md-sys-color-outline-variant: #49454e; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #cfbdfe; --md-sys-color-success: #82d996; --md-sys-color-on-success: #003918; --md-sys-color-success-container: #005226; --md-sys-color-on-success-container: #9ef6b0; --md-sys-color-warning: #ffb869; --md-sys-color-on-warning: #482900; --md-sys-color-warning-container: #673d00; --md-sys-color-on-warning-container: #ffddbb; } }
:root[data-contrast="medium"] { --md-sys-color-primary: #3c2d63; --md-sys-color-on-primary: #ffffff; --md-sys-color-primary-container: #74649f; --md-sys-color-on-primary-container: #ffffff; --md-sys-color-inverse-primary: #cfbdfe; --md-sys-color-secondary: #393347; --md-sys-color-on-secondary: #ffffff; --md-sys-color-secondary-container: #716a80; --md-sys-color-on-secondary-container: #ffffff; --md-sys-color-tertiary: #502b38; --md-sys-color-on-tertiary: #ffffff; --md-sys-color-tertiary-container: #8e606f; --md-sys-color-on-tertiary-container: #ffffff; --md-sys-color-error: #740006; --md-sys-color-on-error: #ffffff; --md-sys-color-error-container: #cf2c27; --md-sys-color-on-error-container: #ffffff; --md-sys-color-background: #fdf7ff; --md-sys-color-on-background: #1d1b20; --md-sys-color-surface: #fdf7ff; --md-sys-color-surface-dim: #cac5cc; --md-sys-color-surface-bright: #fdf7ff; --md-sys-color-surface-container-lowest: #ffffff; --md-sys-color-surface-container-low: #f8f2fa; --md-sys-color-surface-container: #ece6ee; --md-sys-color-surface-container-high: #e1dbe3; --md-sys-color-surface-container-highest: #d5d0d8; --md-sys-color-on-surface: #121016; --md-sys-color-surface-variant: #e7e0eb; --md-sys-color-on-surface-variant: #38353d; --md-sys-color-inverse-surface: #322f35; --md-sys-color-inverse-on-surface: #f5eff7; --md-sys-color-outline: #54515a; --md-sys-color-outline-variant: #6f6b75; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #65558f; --md-sys-color-success: #0a6d36; --md-sys-color-on-success: #ffffff; --md-sys-color-success-container: #9ef6b0; --md-sys-color-on-success-container: #00210b; --md-sys-color-warning: #885200; --md-sys-color-on-warning: #ffffff; --md-sys-color-warning-container: #ffddbb; --md-sys-color-on-warning-container: #2b1700; }
:root[data-contrast="medium"][data-mode="dark"] { --md-sys-color-primary: #e3d6ff; --md-sys-color-on-primary: #2b1b52; --md-sys-color-primary-container: #9887c5; --md-sys-color-on-primary-container: #000000; --md-sys-color-inverse-primary: #4e3f77; --md-sys-color-secondary: #e2d8f2; --md-sys-color-on-secondary: #282336; --md-sys-color-secondary-container: #958da4; --md-sys-color-on-secondary-container: #000000; --md-sys-color-tertiary: #ffd0dd; --md-sys-color-on-tertiary: #3d1a27; --md-sys-color-tertiary-container: #b58392; --md-sys-color-on-tertiary-container: #000000; --md-sys-color-error: #ffd2cc; --md-sys-color-on-error: #540003; --md-sys-color-error-container: #ff5449; --md-sys-color-on-error-container: #000000; --md-sys-color-background: #141218; --md-sys-color-on-background: #e6e0e9; --md-sys-color-surface: #141218; --md-sys-color-surface-dim: #141218; --md-sys-color-surface-bright: #46434a; --md-sys-color-surface-container-lowest: #08070b; --md-sys-color-surface-container-low: #1f1d22; --md-sys-color-surface-container: #29272d; --md-sys-color-surface-container-high: #343138; --md-sys-color-surface-container-highest: #3f3c43; --md-sys-color-on-surface: #ffffff; --md-sys-color-surface-variant: #49454e; --md-sys-color-on-surface-variant: #e0dae5; --md-sys-color-inverse-surface: #e6e0e9; --md-sys-color-inverse-on-surface: #2b292f; --md-sys-color-outline: #b5b0bb; --md-sys-color-outline-variant: #938e99; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #cfbdfe; --md-sys-color-success: #82d996; --md-sys-color-on-success: #003918; --md-sys-color-success-container: #005226; --md-sys-color-on-success-container: #9ef6b0; --md-sys-color-warning: #ffb869; --md-sys-color-on-warning: #482900; --md-sys-color-warning-container: #673d00; --md-sys-color-on-warning-container: #ffddbb; }
@media (prefers-color-scheme: dark) { :root[data-contrast="medium"]:not([data-mode="light"]) { --md-sys-color-primary: #e3d6ff; --md-sys-color-on-primary: #2b1b52; --md-sys-color-primary-container: #9887c5; --md-sys-color-on-primary-container: #000000; --md-sys-color-inverse-primary: #4e3f77; --md-sys-color-secondary: #e2d8f2; --md-sys-color-on-secondary: #282336; --md-sys-color-secondary-container: #958da4; --md-sys-color-on-secondary-container: #000000; --md-sys-color-tertiary: #ffd0dd; --md-sys-color-on-tertiary: #3d1a27; --md-sys-color-tertiary-container: #b58392; --md-sys-color-on-tertiary-container: #000000; --md-sys-color-error: #ffd2cc; --md-sys-color-on-error: #540003; --md-sys-color-error-container: #ff5449; --md-sys-color-on-error-container: #000000; --md-sys-color-background: #141218; --md-sys-color-on-background: #e6e0e9; --md-sys-color-surface: #141218; --md-sys-color-surface-dim: #141218; --md-sys-color-surface-bright: #46434a; --md-sys-color-surface-container-lowest: #08070b; --md-sys-color-surface-container-low: #1f1d22; --md-sys-color-surface-container: #29272d; --md-sys-color-surface-container-high: #343138; --md-sys-color-surface-container-highest: #3f3c43; --md-sys-color-on-surface: #ffffff; --md-sys-color-surface-variant: #49454e; --md-sys-color-on-surface-variant: #e0dae5; --md-sys-color-inverse-surface: #e6e0e9; --md-sys-color-inverse-on-surface: #2b292f; --md-sys-color-outline: #b5b0bb; --md-sys-color-outline-variant: #938e99; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #cfbdfe; --md-sys-color-success: #82d996; --md-sys-color-on-success: #003918; --md-sys-color-success-container: #005226; --md-sys-color-on-success-container: #9ef6b0; --md-sys-color-warning: #ffb869; --md-sys-color-on-warning: #482900; --md-sys-color-warning-container: #673d00; --md-sys-color-on-warning-container: #ffddbb; } }
:root[data-contrast="high"] { --md-sys-color-primary: #312259; --md-sys-color-on-primary: #ffffff; --md-sys-color-primary-container: #4f4078; --md-sys-color-on-primary-container: #ffffff; --md-sys-color-inverse-primary: #cfbdfe; --md-sys-color-secondary: #2f293c; --md-sys-color-on-secondary: #ffffff; --md-sys-color-secondary-container: #4c465b; --md-sys-color-on-secondary-container: #ffffff; --md-sys-color-tertiary: #45212e; --md-sys-color-on-tertiary: #ffffff; --md-sys-color-tertiary-container: #663d4b; --md-sys-color-on-tertiary-container: #ffffff; --md-sys-color-error: #600004; --md-sys-color-on-error: #ffffff; --md-sys-color-error-container: #98000a; --md-sys-color-on-error-container: #ffffff; --md-sys-color-background: #fdf7ff; --md-sys-color-on-background: #1d1b20; --md-sys-color-surface: #fdf7ff; --md-sys-color-surface-dim: #bcb7bf; --md-sys-color-surface-bright: #fdf7ff; --md-sys-color-surface-container-lowest: #ffffff; --md-sys-color-surface-container-low: #f5eff7; --md-sys-color-surface-container: #e6e0e9; --md-sys-color-surface-container-high: #d8d2da; --md-sys-color-surface-container-highest: #cac5cc; --md-sys-color-on-surface: #000000; --md-sys-color-surface-variant: #e7e0eb; --md-sys-color-on-surface-variant: #000000; --md-sys-color-inverse-surface: #322f35; --md-sys-color-inverse-on-surface: #ffffff; --md-sys-color-outline: #2e2b33; --md-sys-color-outline-variant: #4b4851; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #65558f; --md-sys-color-success: #0a6d36; --md-sys-color-on-success: #ffffff; --md-sys-color-success-container: #9ef6b0; --md-sys-color-on-success-container: #00210b; --md-sys-color-warning: #885200; --md-sys-color-on-warning: #ffffff; --md-sys-color-warning-container: #ffddbb; --md-sys-color-on-warning-container: #2b1700; }
:root[data-contrast="high"][data-mode="dark"] { --md-sys-color-primary: #f5edff; --md-sys-color-on-primary: #000000; --md-sys-color-primary-container: #cbb9fa; --md-sys-color-on-primary-container: #0f0033; --md-sys-color-inverse-primary: #4e3f77; --md-sys-color-secondary: #f5edff; --md-sys-color-on-secondary: #000000; --md-sys-color-secondary-container: #c8bfd8; --md-sys-color-on-secondary-container: #0d081a; --md-sys-color-tertiary: #ffebef; --md-sys-color-on-tertiary: #000000; --md-sys-color-tertiary-container: #ebb4c4; --md-sys-color-on-tertiary-container: #1d020d; --md-sys-color-error: #ffece9; --md-sys-color-on-error: #000000; --md-sys-color-error-container: #ffaea4; --md-sys-color-on-error-container: #220001; --md-sys-color-background: #141218; --md-sys-color-on-background: #e6e0e9; --md-sys-color-surface: #141218; --md-sys-color-surface-dim: #141218; --md-sys-color-surface-bright: #524f55; --md-sys-color-surface-container-lowest: #000000; --md-sys-color-surface-container-low: #211f24; --md-sys-color-surface-container: #322f35; --md-sys-color-surface-container-high: #3d3a41; --md-sys-color-surface-container-highest: #48464c; --md-sys-color-on-surface: #ffffff; --md-sys-color-surface-variant: #49454e; --md-sys-color-on-surface-variant: #ffffff; --md-sys-color-inverse-surface: #e6e0e9; --md-sys-color-inverse-on-surface: #000000; --md-sys-color-outline: #f4eef9; --md-sys-color-outline-variant: #c6c0cb; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #cfbdfe; --md-sys-color-success: #82d996; --md-sys-color-on-success: #003918; --md-sys-color-success-container: #005226; --md-sys-color-on-success-container: #9ef6b0; --md-sys-color-warning: #ffb869; --md-sys-color-on-warning: #482900; --md-sys-color-warning-container: #673d00; --md-sys-color-on-warning-container: #ffddbb; }
@media (prefers-color-scheme: dark) { :root[data-contrast="high"]:not([data-mode="light"]) { --md-sys-color-primary: #f5edff; --md-sys-color-on-primary: #000000; --md-sys-color-primary-container: #cbb9fa; --md-sys-color-on-primary-container: #0f0033; --md-sys-color-inverse-primary: #4e3f77; --md-sys-color-secondary: #f5edff; --md-sys-color-on-secondary: #000000; --md-sys-color-secondary-container: #c8bfd8; --md-sys-color-on-secondary-container: #0d081a; --md-sys-color-tertiary: #ffebef; --md-sys-color-on-tertiary: #000000; --md-sys-color-tertiary-container: #ebb4c4; --md-sys-color-on-tertiary-container: #1d020d; --md-sys-color-error: #ffece9; --md-sys-color-on-error: #000000; --md-sys-color-error-container: #ffaea4; --md-sys-color-on-error-container: #220001; --md-sys-color-background: #141218; --md-sys-color-on-background: #e6e0e9; --md-sys-color-surface: #141218; --md-sys-color-surface-dim: #141218; --md-sys-color-surface-bright: #524f55; --md-sys-color-surface-container-lowest: #000000; --md-sys-color-surface-container-low: #211f24; --md-sys-color-surface-container: #322f35; --md-sys-color-surface-container-high: #3d3a41; --md-sys-color-surface-container-highest: #48464c; --md-sys-color-on-surface: #ffffff; --md-sys-color-surface-variant: #49454e; --md-sys-color-on-surface-variant: #ffffff; --md-sys-color-inverse-surface: #e6e0e9; --md-sys-color-inverse-on-surface: #000000; --md-sys-color-outline: #f4eef9; --md-sys-color-outline-variant: #c6c0cb; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #cfbdfe; --md-sys-color-success: #82d996; --md-sys-color-on-success: #003918; --md-sys-color-success-container: #005226; --md-sys-color-on-success-container: #9ef6b0; --md-sys-color-warning: #ffb869; --md-sys-color-on-warning: #482900; --md-sys-color-warning-container: #673d00; --md-sys-color-on-warning-container: #ffddbb; } }
/* blue (seed #0061A4) */ /* blue (seed #0061A4) */
:root[data-palette="blue"] { --md-sys-color-primary: #36618e; --md-sys-color-on-primary: #ffffff; --md-sys-color-primary-container: #d1e4ff; --md-sys-color-on-primary-container: #1a4975; --md-sys-color-inverse-primary: #a0cafd; --md-sys-color-secondary: #535f70; --md-sys-color-on-secondary: #ffffff; --md-sys-color-secondary-container: #d7e3f8; --md-sys-color-on-secondary-container: #3b4858; --md-sys-color-tertiary: #6b5778; --md-sys-color-on-tertiary: #ffffff; --md-sys-color-tertiary-container: #f3daff; --md-sys-color-on-tertiary-container: #523f5f; --md-sys-color-error: #ba1a1a; --md-sys-color-on-error: #ffffff; --md-sys-color-error-container: #ffdad6; --md-sys-color-on-error-container: #93000a; --md-sys-color-background: #f8f9ff; --md-sys-color-on-background: #191c20; --md-sys-color-surface: #f8f9ff; --md-sys-color-surface-dim: #d8dae0; --md-sys-color-surface-bright: #f8f9ff; --md-sys-color-surface-container-lowest: #ffffff; --md-sys-color-surface-container-low: #f2f3fa; --md-sys-color-surface-container: #eceef4; --md-sys-color-surface-container-high: #e6e8ee; --md-sys-color-surface-container-highest: #e1e2e8; --md-sys-color-on-surface: #191c20; --md-sys-color-surface-variant: #dfe2eb; --md-sys-color-on-surface-variant: #43474e; --md-sys-color-inverse-surface: #2e3135; --md-sys-color-inverse-on-surface: #eff0f7; --md-sys-color-outline: #73777f; --md-sys-color-outline-variant: #c3c6cf; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #36618e; --md-sys-color-success: #0a6d36; --md-sys-color-on-success: #ffffff; --md-sys-color-success-container: #9ef6b0; --md-sys-color-on-success-container: #00210b; --md-sys-color-warning: #6f5d00; --md-sys-color-on-warning: #ffffff; --md-sys-color-warning-container: #ffe16a; --md-sys-color-on-warning-container: #221b00; } :root[data-palette="blue"] { --md-sys-color-primary: #36618e; --md-sys-color-on-primary: #ffffff; --md-sys-color-primary-container: #d1e4ff; --md-sys-color-on-primary-container: #1a4975; --md-sys-color-inverse-primary: #a0cafd; --md-sys-color-secondary: #535f70; --md-sys-color-on-secondary: #ffffff; --md-sys-color-secondary-container: #d7e3f8; --md-sys-color-on-secondary-container: #3b4858; --md-sys-color-tertiary: #6b5778; --md-sys-color-on-tertiary: #ffffff; --md-sys-color-tertiary-container: #f3daff; --md-sys-color-on-tertiary-container: #523f5f; --md-sys-color-error: #ba1a1a; --md-sys-color-on-error: #ffffff; --md-sys-color-error-container: #ffdad6; --md-sys-color-on-error-container: #93000a; --md-sys-color-background: #f8f9ff; --md-sys-color-on-background: #191c20; --md-sys-color-surface: #f8f9ff; --md-sys-color-surface-dim: #d8dae0; --md-sys-color-surface-bright: #f8f9ff; --md-sys-color-surface-container-lowest: #ffffff; --md-sys-color-surface-container-low: #f2f3fa; --md-sys-color-surface-container: #eceef4; --md-sys-color-surface-container-high: #e6e8ee; --md-sys-color-surface-container-highest: #e1e2e8; --md-sys-color-on-surface: #191c20; --md-sys-color-surface-variant: #dfe2eb; --md-sys-color-on-surface-variant: #43474e; --md-sys-color-inverse-surface: #2e3135; --md-sys-color-inverse-on-surface: #eff0f7; --md-sys-color-outline: #73777f; --md-sys-color-outline-variant: #c3c6cf; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #36618e; --md-sys-color-success: #0a6d36; --md-sys-color-on-success: #ffffff; --md-sys-color-success-container: #9ef6b0; --md-sys-color-on-success-container: #00210b; --md-sys-color-warning: #6f5d00; --md-sys-color-on-warning: #ffffff; --md-sys-color-warning-container: #ffe16a; --md-sys-color-on-warning-container: #221b00; }
:root[data-palette="blue"][data-mode="dark"] { --md-sys-color-primary: #a0cafd; --md-sys-color-on-primary: #003258; --md-sys-color-primary-container: #1a4975; --md-sys-color-on-primary-container: #d1e4ff; --md-sys-color-inverse-primary: #36618e; --md-sys-color-secondary: #bbc7db; --md-sys-color-on-secondary: #253140; --md-sys-color-secondary-container: #3b4858; --md-sys-color-on-secondary-container: #d7e3f8; --md-sys-color-tertiary: #d7bee4; --md-sys-color-on-tertiary: #3b2948; --md-sys-color-tertiary-container: #523f5f; --md-sys-color-on-tertiary-container: #f3daff; --md-sys-color-error: #ffb4ab; --md-sys-color-on-error: #690005; --md-sys-color-error-container: #93000a; --md-sys-color-on-error-container: #ffdad6; --md-sys-color-background: #111418; --md-sys-color-on-background: #e1e2e8; --md-sys-color-surface: #111418; --md-sys-color-surface-dim: #111418; --md-sys-color-surface-bright: #36393e; --md-sys-color-surface-container-lowest: #0b0e13; --md-sys-color-surface-container-low: #191c20; --md-sys-color-surface-container: #1d2024; --md-sys-color-surface-container-high: #272a2f; --md-sys-color-surface-container-highest: #32353a; --md-sys-color-on-surface: #e1e2e8; --md-sys-color-surface-variant: #43474e; --md-sys-color-on-surface-variant: #c3c6cf; --md-sys-color-inverse-surface: #e1e2e8; --md-sys-color-inverse-on-surface: #2e3135; --md-sys-color-outline: #8d9199; --md-sys-color-outline-variant: #43474e; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #a0cafd; --md-sys-color-success: #82d996; --md-sys-color-on-success: #003918; --md-sys-color-success-container: #005226; --md-sys-color-on-success-container: #9ef6b0; --md-sys-color-warning: #e2c54a; --md-sys-color-on-warning: #3a3000; --md-sys-color-warning-container: #544600; --md-sys-color-on-warning-container: #ffe16a; } :root[data-palette="blue"][data-mode="dark"] { --md-sys-color-primary: #a0cafd; --md-sys-color-on-primary: #003258; --md-sys-color-primary-container: #1a4975; --md-sys-color-on-primary-container: #d1e4ff; --md-sys-color-inverse-primary: #36618e; --md-sys-color-secondary: #bbc7db; --md-sys-color-on-secondary: #253140; --md-sys-color-secondary-container: #3b4858; --md-sys-color-on-secondary-container: #d7e3f8; --md-sys-color-tertiary: #d7bee4; --md-sys-color-on-tertiary: #3b2948; --md-sys-color-tertiary-container: #523f5f; --md-sys-color-on-tertiary-container: #f3daff; --md-sys-color-error: #ffb4ab; --md-sys-color-on-error: #690005; --md-sys-color-error-container: #93000a; --md-sys-color-on-error-container: #ffdad6; --md-sys-color-background: #111418; --md-sys-color-on-background: #e1e2e8; --md-sys-color-surface: #111418; --md-sys-color-surface-dim: #111418; --md-sys-color-surface-bright: #36393e; --md-sys-color-surface-container-lowest: #0b0e13; --md-sys-color-surface-container-low: #191c20; --md-sys-color-surface-container: #1d2024; --md-sys-color-surface-container-high: #272a2f; --md-sys-color-surface-container-highest: #32353a; --md-sys-color-on-surface: #e1e2e8; --md-sys-color-surface-variant: #43474e; --md-sys-color-on-surface-variant: #c3c6cf; --md-sys-color-inverse-surface: #e1e2e8; --md-sys-color-inverse-on-surface: #2e3135; --md-sys-color-outline: #8d9199; --md-sys-color-outline-variant: #43474e; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #a0cafd; --md-sys-color-success: #82d996; --md-sys-color-on-success: #003918; --md-sys-color-success-container: #005226; --md-sys-color-on-success-container: #9ef6b0; --md-sys-color-warning: #e2c54a; --md-sys-color-on-warning: #3a3000; --md-sys-color-warning-container: #544600; --md-sys-color-on-warning-container: #ffe16a; }
@media (prefers-color-scheme: dark) { :root[data-palette="blue"]:not([data-mode="light"]) { --md-sys-color-primary: #a0cafd; --md-sys-color-on-primary: #003258; --md-sys-color-primary-container: #1a4975; --md-sys-color-on-primary-container: #d1e4ff; --md-sys-color-inverse-primary: #36618e; --md-sys-color-secondary: #bbc7db; --md-sys-color-on-secondary: #253140; --md-sys-color-secondary-container: #3b4858; --md-sys-color-on-secondary-container: #d7e3f8; --md-sys-color-tertiary: #d7bee4; --md-sys-color-on-tertiary: #3b2948; --md-sys-color-tertiary-container: #523f5f; --md-sys-color-on-tertiary-container: #f3daff; --md-sys-color-error: #ffb4ab; --md-sys-color-on-error: #690005; --md-sys-color-error-container: #93000a; --md-sys-color-on-error-container: #ffdad6; --md-sys-color-background: #111418; --md-sys-color-on-background: #e1e2e8; --md-sys-color-surface: #111418; --md-sys-color-surface-dim: #111418; --md-sys-color-surface-bright: #36393e; --md-sys-color-surface-container-lowest: #0b0e13; --md-sys-color-surface-container-low: #191c20; --md-sys-color-surface-container: #1d2024; --md-sys-color-surface-container-high: #272a2f; --md-sys-color-surface-container-highest: #32353a; --md-sys-color-on-surface: #e1e2e8; --md-sys-color-surface-variant: #43474e; --md-sys-color-on-surface-variant: #c3c6cf; --md-sys-color-inverse-surface: #e1e2e8; --md-sys-color-inverse-on-surface: #2e3135; --md-sys-color-outline: #8d9199; --md-sys-color-outline-variant: #43474e; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #a0cafd; --md-sys-color-success: #82d996; --md-sys-color-on-success: #003918; --md-sys-color-success-container: #005226; --md-sys-color-on-success-container: #9ef6b0; --md-sys-color-warning: #e2c54a; --md-sys-color-on-warning: #3a3000; --md-sys-color-warning-container: #544600; --md-sys-color-on-warning-container: #ffe16a; } } @media (prefers-color-scheme: dark) { :root[data-palette="blue"]:not([data-mode="light"]) { --md-sys-color-primary: #a0cafd; --md-sys-color-on-primary: #003258; --md-sys-color-primary-container: #1a4975; --md-sys-color-on-primary-container: #d1e4ff; --md-sys-color-inverse-primary: #36618e; --md-sys-color-secondary: #bbc7db; --md-sys-color-on-secondary: #253140; --md-sys-color-secondary-container: #3b4858; --md-sys-color-on-secondary-container: #d7e3f8; --md-sys-color-tertiary: #d7bee4; --md-sys-color-on-tertiary: #3b2948; --md-sys-color-tertiary-container: #523f5f; --md-sys-color-on-tertiary-container: #f3daff; --md-sys-color-error: #ffb4ab; --md-sys-color-on-error: #690005; --md-sys-color-error-container: #93000a; --md-sys-color-on-error-container: #ffdad6; --md-sys-color-background: #111418; --md-sys-color-on-background: #e1e2e8; --md-sys-color-surface: #111418; --md-sys-color-surface-dim: #111418; --md-sys-color-surface-bright: #36393e; --md-sys-color-surface-container-lowest: #0b0e13; --md-sys-color-surface-container-low: #191c20; --md-sys-color-surface-container: #1d2024; --md-sys-color-surface-container-high: #272a2f; --md-sys-color-surface-container-highest: #32353a; --md-sys-color-on-surface: #e1e2e8; --md-sys-color-surface-variant: #43474e; --md-sys-color-on-surface-variant: #c3c6cf; --md-sys-color-inverse-surface: #e1e2e8; --md-sys-color-inverse-on-surface: #2e3135; --md-sys-color-outline: #8d9199; --md-sys-color-outline-variant: #43474e; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #a0cafd; --md-sys-color-success: #82d996; --md-sys-color-on-success: #003918; --md-sys-color-success-container: #005226; --md-sys-color-on-success-container: #9ef6b0; --md-sys-color-warning: #e2c54a; --md-sys-color-on-warning: #3a3000; --md-sys-color-warning-container: #544600; --md-sys-color-on-warning-container: #ffe16a; } }
:root[data-palette="blue"][data-contrast="medium"] { --md-sys-color-primary: #003862; --md-sys-color-on-primary: #ffffff; --md-sys-color-primary-container: #46709e; --md-sys-color-on-primary-container: #ffffff; --md-sys-color-inverse-primary: #a0cafd; --md-sys-color-secondary: #2b3746; --md-sys-color-on-secondary: #ffffff; --md-sys-color-secondary-container: #626e7f; --md-sys-color-on-secondary-container: #ffffff; --md-sys-color-tertiary: #412f4e; --md-sys-color-on-tertiary: #ffffff; --md-sys-color-tertiary-container: #7a6587; --md-sys-color-on-tertiary-container: #ffffff; --md-sys-color-error: #740006; --md-sys-color-on-error: #ffffff; --md-sys-color-error-container: #cf2c27; --md-sys-color-on-error-container: #ffffff; --md-sys-color-background: #f8f9ff; --md-sys-color-on-background: #191c20; --md-sys-color-surface: #f8f9ff; --md-sys-color-surface-dim: #c4c6cc; --md-sys-color-surface-bright: #f8f9ff; --md-sys-color-surface-container-lowest: #ffffff; --md-sys-color-surface-container-low: #f2f3fa; --md-sys-color-surface-container: #e6e8ee; --md-sys-color-surface-container-high: #dbdde3; --md-sys-color-surface-container-highest: #d0d1d7; --md-sys-color-on-surface: #0e1116; --md-sys-color-surface-variant: #dfe2eb; --md-sys-color-on-surface-variant: #32363d; --md-sys-color-inverse-surface: #2e3135; --md-sys-color-inverse-on-surface: #eff0f7; --md-sys-color-outline: #4e535a; --md-sys-color-outline-variant: #696d75; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #36618e; --md-sys-color-success: #0a6d36; --md-sys-color-on-success: #ffffff; --md-sys-color-success-container: #9ef6b0; --md-sys-color-on-success-container: #00210b; --md-sys-color-warning: #6f5d00; --md-sys-color-on-warning: #ffffff; --md-sys-color-warning-container: #ffe16a; --md-sys-color-on-warning-container: #221b00; }
/* teal (seed #006A6A) */ :root[data-palette="blue"][data-contrast="medium"][data-mode="dark"] { --md-sys-color-primary: #c7deff; --md-sys-color-on-primary: #002747; --md-sys-color-primary-container: #6b94c4; --md-sys-color-on-primary-container: #000000; --md-sys-color-inverse-primary: #1b4a76; --md-sys-color-secondary: #d0ddf1; --md-sys-color-on-secondary: #1a2635; --md-sys-color-secondary-container: #8592a4; --md-sys-color-on-secondary-container: #000000; --md-sys-color-tertiary: #edd3fb; --md-sys-color-on-tertiary: #301e3c; --md-sys-color-tertiary-container: #9f88ad; --md-sys-color-on-tertiary-container: #000000; --md-sys-color-error: #ffd2cc; --md-sys-color-on-error: #540003; --md-sys-color-error-container: #ff5449; --md-sys-color-on-error-container: #000000; --md-sys-color-background: #111418; --md-sys-color-on-background: #e1e2e8; --md-sys-color-surface: #111418; --md-sys-color-surface-dim: #111418; --md-sys-color-surface-bright: #42454a; --md-sys-color-surface-container-lowest: #05080b; --md-sys-color-surface-container-low: #1b1e22; --md-sys-color-surface-container: #25282d; --md-sys-color-surface-container-high: #303337; --md-sys-color-surface-container-highest: #3b3e43; --md-sys-color-on-surface: #ffffff; --md-sys-color-surface-variant: #43474e; --md-sys-color-on-surface-variant: #d9dce5; --md-sys-color-inverse-surface: #e1e2e8; --md-sys-color-inverse-on-surface: #272a2f; --md-sys-color-outline: #aeb2ba; --md-sys-color-outline-variant: #8c9098; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #a0cafd; --md-sys-color-success: #82d996; --md-sys-color-on-success: #003918; --md-sys-color-success-container: #005226; --md-sys-color-on-success-container: #9ef6b0; --md-sys-color-warning: #e2c54a; --md-sys-color-on-warning: #3a3000; --md-sys-color-warning-container: #544600; --md-sys-color-on-warning-container: #ffe16a; }
:root[data-palette="teal"] { --md-sys-color-primary: #006a6a; --md-sys-color-on-primary: #ffffff; --md-sys-color-primary-container: #9cf1f0; --md-sys-color-on-primary-container: #004f4f; --md-sys-color-inverse-primary: #80d5d4; --md-sys-color-secondary: #4a6363; --md-sys-color-on-secondary: #ffffff; --md-sys-color-secondary-container: #cce8e7; --md-sys-color-on-secondary-container: #324b4b; --md-sys-color-tertiary: #4b607c; --md-sys-color-on-tertiary: #ffffff; --md-sys-color-tertiary-container: #d3e4ff; --md-sys-color-on-tertiary-container: #334863; --md-sys-color-error: #ba1a1a; --md-sys-color-on-error: #ffffff; --md-sys-color-error-container: #ffdad6; --md-sys-color-on-error-container: #93000a; --md-sys-color-background: #f4fbfa; --md-sys-color-on-background: #161d1d; --md-sys-color-surface: #f4fbfa; --md-sys-color-surface-dim: #d5dbda; --md-sys-color-surface-bright: #f4fbfa; --md-sys-color-surface-container-lowest: #ffffff; --md-sys-color-surface-container-low: #eff5f4; --md-sys-color-surface-container: #e9efee; --md-sys-color-surface-container-high: #e3e9e9; --md-sys-color-surface-container-highest: #dde4e3; --md-sys-color-on-surface: #161d1d; --md-sys-color-surface-variant: #dae5e4; --md-sys-color-on-surface-variant: #3f4948; --md-sys-color-inverse-surface: #2b3231; --md-sys-color-inverse-on-surface: #ecf2f1; --md-sys-color-outline: #6f7979; --md-sys-color-outline-variant: #bec9c8; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #006a6a; --md-sys-color-success: #0a6d36; --md-sys-color-on-success: #ffffff; --md-sys-color-success-container: #9ef6b0; --md-sys-color-on-success-container: #00210b; --md-sys-color-warning: #6f5d00; --md-sys-color-on-warning: #ffffff; --md-sys-color-warning-container: #ffe16a; --md-sys-color-on-warning-container: #221b00; } @media (prefers-color-scheme: dark) { :root[data-palette="blue"][data-contrast="medium"]:not([data-mode="light"]) { --md-sys-color-primary: #c7deff; --md-sys-color-on-primary: #002747; --md-sys-color-primary-container: #6b94c4; --md-sys-color-on-primary-container: #000000; --md-sys-color-inverse-primary: #1b4a76; --md-sys-color-secondary: #d0ddf1; --md-sys-color-on-secondary: #1a2635; --md-sys-color-secondary-container: #8592a4; --md-sys-color-on-secondary-container: #000000; --md-sys-color-tertiary: #edd3fb; --md-sys-color-on-tertiary: #301e3c; --md-sys-color-tertiary-container: #9f88ad; --md-sys-color-on-tertiary-container: #000000; --md-sys-color-error: #ffd2cc; --md-sys-color-on-error: #540003; --md-sys-color-error-container: #ff5449; --md-sys-color-on-error-container: #000000; --md-sys-color-background: #111418; --md-sys-color-on-background: #e1e2e8; --md-sys-color-surface: #111418; --md-sys-color-surface-dim: #111418; --md-sys-color-surface-bright: #42454a; --md-sys-color-surface-container-lowest: #05080b; --md-sys-color-surface-container-low: #1b1e22; --md-sys-color-surface-container: #25282d; --md-sys-color-surface-container-high: #303337; --md-sys-color-surface-container-highest: #3b3e43; --md-sys-color-on-surface: #ffffff; --md-sys-color-surface-variant: #43474e; --md-sys-color-on-surface-variant: #d9dce5; --md-sys-color-inverse-surface: #e1e2e8; --md-sys-color-inverse-on-surface: #272a2f; --md-sys-color-outline: #aeb2ba; --md-sys-color-outline-variant: #8c9098; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #a0cafd; --md-sys-color-success: #82d996; --md-sys-color-on-success: #003918; --md-sys-color-success-container: #005226; --md-sys-color-on-success-container: #9ef6b0; --md-sys-color-warning: #e2c54a; --md-sys-color-on-warning: #3a3000; --md-sys-color-warning-container: #544600; --md-sys-color-on-warning-container: #ffe16a; } }
:root[data-palette="teal"][data-mode="dark"] { --md-sys-color-primary: #80d5d4; --md-sys-color-on-primary: #003737; --md-sys-color-primary-container: #004f4f; --md-sys-color-on-primary-container: #9cf1f0; --md-sys-color-inverse-primary: #006a6a; --md-sys-color-secondary: #b0cccb; --md-sys-color-on-secondary: #1b3534; --md-sys-color-secondary-container: #324b4b; --md-sys-color-on-secondary-container: #cce8e7; --md-sys-color-tertiary: #b3c8e8; --md-sys-color-on-tertiary: #1c314b; --md-sys-color-tertiary-container: #334863; --md-sys-color-on-tertiary-container: #d3e4ff; --md-sys-color-error: #ffb4ab; --md-sys-color-on-error: #690005; --md-sys-color-error-container: #93000a; --md-sys-color-on-error-container: #ffdad6; --md-sys-color-background: #0e1514; --md-sys-color-on-background: #dde4e3; --md-sys-color-surface: #0e1514; --md-sys-color-surface-dim: #0e1514; --md-sys-color-surface-bright: #343a3a; --md-sys-color-surface-container-lowest: #090f0f; --md-sys-color-surface-container-low: #161d1d; --md-sys-color-surface-container: #1a2121; --md-sys-color-surface-container-high: #252b2b; --md-sys-color-surface-container-highest: #2f3636; --md-sys-color-on-surface: #dde4e3; --md-sys-color-surface-variant: #3f4948; --md-sys-color-on-surface-variant: #bec9c8; --md-sys-color-inverse-surface: #dde4e3; --md-sys-color-inverse-on-surface: #2b3231; --md-sys-color-outline: #889392; --md-sys-color-outline-variant: #3f4948; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #80d5d4; --md-sys-color-success: #82d996; --md-sys-color-on-success: #003918; --md-sys-color-success-container: #005226; --md-sys-color-on-success-container: #9ef6b0; --md-sys-color-warning: #e2c54a; --md-sys-color-on-warning: #3a3000; --md-sys-color-warning-container: #544600; --md-sys-color-on-warning-container: #ffe16a; } :root[data-palette="blue"][data-contrast="high"] { --md-sys-color-primary: #002e51; --md-sys-color-on-primary: #ffffff; --md-sys-color-primary-container: #1d4b77; --md-sys-color-on-primary-container: #ffffff; --md-sys-color-inverse-primary: #a0cafd; --md-sys-color-secondary: #212d3c; --md-sys-color-on-secondary: #ffffff; --md-sys-color-secondary-container: #3e4a5a; --md-sys-color-on-secondary-container: #ffffff; --md-sys-color-tertiary: #362543; --md-sys-color-on-tertiary: #ffffff; --md-sys-color-tertiary-container: #554262; --md-sys-color-on-tertiary-container: #ffffff; --md-sys-color-error: #600004; --md-sys-color-on-error: #ffffff; --md-sys-color-error-container: #98000a; --md-sys-color-on-error-container: #ffffff; --md-sys-color-background: #f8f9ff; --md-sys-color-on-background: #191c20; --md-sys-color-surface: #f8f9ff; --md-sys-color-surface-dim: #b7b9be; --md-sys-color-surface-bright: #f8f9ff; --md-sys-color-surface-container-lowest: #ffffff; --md-sys-color-surface-container-low: #eff0f7; --md-sys-color-surface-container: #e1e2e8; --md-sys-color-surface-container-high: #d3d4da; --md-sys-color-surface-container-highest: #c4c6cc; --md-sys-color-on-surface: #000000; --md-sys-color-surface-variant: #dfe2eb; --md-sys-color-on-surface-variant: #000000; --md-sys-color-inverse-surface: #2e3135; --md-sys-color-inverse-on-surface: #ffffff; --md-sys-color-outline: #282c33; --md-sys-color-outline-variant: #454950; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #36618e; --md-sys-color-success: #0a6d36; --md-sys-color-on-success: #ffffff; --md-sys-color-success-container: #9ef6b0; --md-sys-color-on-success-container: #00210b; --md-sys-color-warning: #6f5d00; --md-sys-color-on-warning: #ffffff; --md-sys-color-warning-container: #ffe16a; --md-sys-color-on-warning-container: #221b00; }
@media (prefers-color-scheme: dark) { :root[data-palette="teal"]:not([data-mode="light"]) { --md-sys-color-primary: #80d5d4; --md-sys-color-on-primary: #003737; --md-sys-color-primary-container: #004f4f; --md-sys-color-on-primary-container: #9cf1f0; --md-sys-color-inverse-primary: #006a6a; --md-sys-color-secondary: #b0cccb; --md-sys-color-on-secondary: #1b3534; --md-sys-color-secondary-container: #324b4b; --md-sys-color-on-secondary-container: #cce8e7; --md-sys-color-tertiary: #b3c8e8; --md-sys-color-on-tertiary: #1c314b; --md-sys-color-tertiary-container: #334863; --md-sys-color-on-tertiary-container: #d3e4ff; --md-sys-color-error: #ffb4ab; --md-sys-color-on-error: #690005; --md-sys-color-error-container: #93000a; --md-sys-color-on-error-container: #ffdad6; --md-sys-color-background: #0e1514; --md-sys-color-on-background: #dde4e3; --md-sys-color-surface: #0e1514; --md-sys-color-surface-dim: #0e1514; --md-sys-color-surface-bright: #343a3a; --md-sys-color-surface-container-lowest: #090f0f; --md-sys-color-surface-container-low: #161d1d; --md-sys-color-surface-container: #1a2121; --md-sys-color-surface-container-high: #252b2b; --md-sys-color-surface-container-highest: #2f3636; --md-sys-color-on-surface: #dde4e3; --md-sys-color-surface-variant: #3f4948; --md-sys-color-on-surface-variant: #bec9c8; --md-sys-color-inverse-surface: #dde4e3; --md-sys-color-inverse-on-surface: #2b3231; --md-sys-color-outline: #889392; --md-sys-color-outline-variant: #3f4948; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #80d5d4; --md-sys-color-success: #82d996; --md-sys-color-on-success: #003918; --md-sys-color-success-container: #005226; --md-sys-color-on-success-container: #9ef6b0; --md-sys-color-warning: #e2c54a; --md-sys-color-on-warning: #3a3000; --md-sys-color-warning-container: #544600; --md-sys-color-on-warning-container: #ffe16a; } } :root[data-palette="blue"][data-contrast="high"][data-mode="dark"] { --md-sys-color-primary: #e8f0ff; --md-sys-color-on-primary: #000000; --md-sys-color-primary-container: #9dc6f9; --md-sys-color-on-primary-container: #000c1b; --md-sys-color-inverse-primary: #1b4a76; --md-sys-color-secondary: #e8f0ff; --md-sys-color-on-secondary: #000000; --md-sys-color-secondary-container: #b7c4d7; --md-sys-color-on-secondary-container: #010c1a; --md-sys-color-tertiary: #fbebff; --md-sys-color-on-tertiary: #000000; --md-sys-color-tertiary-container: #d3bae0; --md-sys-color-on-tertiary-container: #140420; --md-sys-color-error: #ffece9; --md-sys-color-on-error: #000000; --md-sys-color-error-container: #ffaea4; --md-sys-color-on-error-container: #220001; --md-sys-color-background: #111418; --md-sys-color-on-background: #e1e2e8; --md-sys-color-surface: #111418; --md-sys-color-surface-dim: #111418; --md-sys-color-surface-bright: #4d5055; --md-sys-color-surface-container-lowest: #000000; --md-sys-color-surface-container-low: #1d2024; --md-sys-color-surface-container: #2e3135; --md-sys-color-surface-container-high: #393c40; --md-sys-color-surface-container-highest: #44474c; --md-sys-color-on-surface: #ffffff; --md-sys-color-surface-variant: #43474e; --md-sys-color-on-surface-variant: #ffffff; --md-sys-color-inverse-surface: #e1e2e8; --md-sys-color-inverse-on-surface: #000000; --md-sys-color-outline: #ecf0f9; --md-sys-color-outline-variant: #bfc3cb; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #a0cafd; --md-sys-color-success: #82d996; --md-sys-color-on-success: #003918; --md-sys-color-success-container: #005226; --md-sys-color-on-success-container: #9ef6b0; --md-sys-color-warning: #e2c54a; --md-sys-color-on-warning: #3a3000; --md-sys-color-warning-container: #544600; --md-sys-color-on-warning-container: #ffe16a; }
@media (prefers-color-scheme: dark) { :root[data-palette="blue"][data-contrast="high"]:not([data-mode="light"]) { --md-sys-color-primary: #e8f0ff; --md-sys-color-on-primary: #000000; --md-sys-color-primary-container: #9dc6f9; --md-sys-color-on-primary-container: #000c1b; --md-sys-color-inverse-primary: #1b4a76; --md-sys-color-secondary: #e8f0ff; --md-sys-color-on-secondary: #000000; --md-sys-color-secondary-container: #b7c4d7; --md-sys-color-on-secondary-container: #010c1a; --md-sys-color-tertiary: #fbebff; --md-sys-color-on-tertiary: #000000; --md-sys-color-tertiary-container: #d3bae0; --md-sys-color-on-tertiary-container: #140420; --md-sys-color-error: #ffece9; --md-sys-color-on-error: #000000; --md-sys-color-error-container: #ffaea4; --md-sys-color-on-error-container: #220001; --md-sys-color-background: #111418; --md-sys-color-on-background: #e1e2e8; --md-sys-color-surface: #111418; --md-sys-color-surface-dim: #111418; --md-sys-color-surface-bright: #4d5055; --md-sys-color-surface-container-lowest: #000000; --md-sys-color-surface-container-low: #1d2024; --md-sys-color-surface-container: #2e3135; --md-sys-color-surface-container-high: #393c40; --md-sys-color-surface-container-highest: #44474c; --md-sys-color-on-surface: #ffffff; --md-sys-color-surface-variant: #43474e; --md-sys-color-on-surface-variant: #ffffff; --md-sys-color-inverse-surface: #e1e2e8; --md-sys-color-inverse-on-surface: #000000; --md-sys-color-outline: #ecf0f9; --md-sys-color-outline-variant: #bfc3cb; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #a0cafd; --md-sys-color-success: #82d996; --md-sys-color-on-success: #003918; --md-sys-color-success-container: #005226; --md-sys-color-on-success-container: #9ef6b0; --md-sys-color-warning: #e2c54a; --md-sys-color-on-warning: #3a3000; --md-sys-color-warning-container: #544600; --md-sys-color-on-warning-container: #ffe16a; } }
/* green (seed #386A20) */ /* green (seed #386A20) */
:root[data-palette="green"] { --md-sys-color-primary: #446732; --md-sys-color-on-primary: #ffffff; --md-sys-color-primary-container: #c5efab; --md-sys-color-on-primary-container: #2d4f1c; --md-sys-color-inverse-primary: #a9d291; --md-sys-color-secondary: #55624c; --md-sys-color-on-secondary: #ffffff; --md-sys-color-secondary-container: #d8e7cb; --md-sys-color-on-secondary-container: #3e4a35; --md-sys-color-tertiary: #386666; --md-sys-color-on-tertiary: #ffffff; --md-sys-color-tertiary-container: #bbebec; --md-sys-color-on-tertiary-container: #1e4e4f; --md-sys-color-error: #ba1a1a; --md-sys-color-on-error: #ffffff; --md-sys-color-error-container: #ffdad6; --md-sys-color-on-error-container: #93000a; --md-sys-color-background: #f8faf0; --md-sys-color-on-background: #191d16; --md-sys-color-surface: #f8faf0; --md-sys-color-surface-dim: #d9dbd1; --md-sys-color-surface-bright: #f8faf0; --md-sys-color-surface-container-lowest: #ffffff; --md-sys-color-surface-container-low: #f2f5ea; --md-sys-color-surface-container: #edefe5; --md-sys-color-surface-container-high: #e7e9df; --md-sys-color-surface-container-highest: #e1e4d9; --md-sys-color-on-surface: #191d16; --md-sys-color-surface-variant: #dfe4d7; --md-sys-color-on-surface-variant: #43483e; --md-sys-color-inverse-surface: #2e312b; --md-sys-color-inverse-on-surface: #f0f2e7; --md-sys-color-outline: #74796d; --md-sys-color-outline-variant: #c3c8bb; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #446732; --md-sys-color-success: #386a1f; --md-sys-color-on-success: #ffffff; --md-sys-color-success-container: #b8f397; --md-sys-color-on-success-container: #072100; --md-sys-color-warning: #6f5d00; --md-sys-color-on-warning: #ffffff; --md-sys-color-warning-container: #ffe16a; --md-sys-color-on-warning-container: #221b00; } :root[data-palette="green"] { --md-sys-color-primary: #446732; --md-sys-color-on-primary: #ffffff; --md-sys-color-primary-container: #c5efab; --md-sys-color-on-primary-container: #2d4f1c; --md-sys-color-inverse-primary: #a9d291; --md-sys-color-secondary: #55624c; --md-sys-color-on-secondary: #ffffff; --md-sys-color-secondary-container: #d8e7cb; --md-sys-color-on-secondary-container: #3e4a35; --md-sys-color-tertiary: #386666; --md-sys-color-on-tertiary: #ffffff; --md-sys-color-tertiary-container: #bbebec; --md-sys-color-on-tertiary-container: #1e4e4f; --md-sys-color-error: #ba1a1a; --md-sys-color-on-error: #ffffff; --md-sys-color-error-container: #ffdad6; --md-sys-color-on-error-container: #93000a; --md-sys-color-background: #f8faf0; --md-sys-color-on-background: #191d16; --md-sys-color-surface: #f8faf0; --md-sys-color-surface-dim: #d9dbd1; --md-sys-color-surface-bright: #f8faf0; --md-sys-color-surface-container-lowest: #ffffff; --md-sys-color-surface-container-low: #f2f5ea; --md-sys-color-surface-container: #edefe5; --md-sys-color-surface-container-high: #e7e9df; --md-sys-color-surface-container-highest: #e1e4d9; --md-sys-color-on-surface: #191d16; --md-sys-color-surface-variant: #dfe4d7; --md-sys-color-on-surface-variant: #43483e; --md-sys-color-inverse-surface: #2e312b; --md-sys-color-inverse-on-surface: #f0f2e7; --md-sys-color-outline: #74796d; --md-sys-color-outline-variant: #c3c8bb; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #446732; --md-sys-color-success: #386a1f; --md-sys-color-on-success: #ffffff; --md-sys-color-success-container: #b8f397; --md-sys-color-on-success-container: #072100; --md-sys-color-warning: #6f5d00; --md-sys-color-on-warning: #ffffff; --md-sys-color-warning-container: #ffe16a; --md-sys-color-on-warning-container: #221b00; }
:root[data-palette="green"][data-mode="dark"] { --md-sys-color-primary: #a9d291; --md-sys-color-on-primary: #173807; --md-sys-color-primary-container: #2d4f1c; --md-sys-color-on-primary-container: #c5efab; --md-sys-color-inverse-primary: #446732; --md-sys-color-secondary: #bdcbb0; --md-sys-color-on-secondary: #283420; --md-sys-color-secondary-container: #3e4a35; --md-sys-color-on-secondary-container: #d8e7cb; --md-sys-color-tertiary: #a0cfd0; --md-sys-color-on-tertiary: #003738; --md-sys-color-tertiary-container: #1e4e4f; --md-sys-color-on-tertiary-container: #bbebec; --md-sys-color-error: #ffb4ab; --md-sys-color-on-error: #690005; --md-sys-color-error-container: #93000a; --md-sys-color-on-error-container: #ffdad6; --md-sys-color-background: #11140f; --md-sys-color-on-background: #e1e4d9; --md-sys-color-surface: #11140f; --md-sys-color-surface-dim: #11140f; --md-sys-color-surface-bright: #373a33; --md-sys-color-surface-container-lowest: #0c0f0a; --md-sys-color-surface-container-low: #191d16; --md-sys-color-surface-container: #1d211a; --md-sys-color-surface-container-high: #282b24; --md-sys-color-surface-container-highest: #32362f; --md-sys-color-on-surface: #e1e4d9; --md-sys-color-surface-variant: #43483e; --md-sys-color-on-surface-variant: #c3c8bb; --md-sys-color-inverse-surface: #e1e4d9; --md-sys-color-inverse-on-surface: #2e312b; --md-sys-color-outline: #8d9286; --md-sys-color-outline-variant: #43483e; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #a9d291; --md-sys-color-success: #9cd67e; --md-sys-color-on-success: #113800; --md-sys-color-success-container: #205107; --md-sys-color-on-success-container: #b8f397; --md-sys-color-warning: #e2c54a; --md-sys-color-on-warning: #3a3000; --md-sys-color-warning-container: #544600; --md-sys-color-on-warning-container: #ffe16a; } :root[data-palette="green"][data-mode="dark"] { --md-sys-color-primary: #a9d291; --md-sys-color-on-primary: #173807; --md-sys-color-primary-container: #2d4f1c; --md-sys-color-on-primary-container: #c5efab; --md-sys-color-inverse-primary: #446732; --md-sys-color-secondary: #bdcbb0; --md-sys-color-on-secondary: #283420; --md-sys-color-secondary-container: #3e4a35; --md-sys-color-on-secondary-container: #d8e7cb; --md-sys-color-tertiary: #a0cfd0; --md-sys-color-on-tertiary: #003738; --md-sys-color-tertiary-container: #1e4e4f; --md-sys-color-on-tertiary-container: #bbebec; --md-sys-color-error: #ffb4ab; --md-sys-color-on-error: #690005; --md-sys-color-error-container: #93000a; --md-sys-color-on-error-container: #ffdad6; --md-sys-color-background: #11140f; --md-sys-color-on-background: #e1e4d9; --md-sys-color-surface: #11140f; --md-sys-color-surface-dim: #11140f; --md-sys-color-surface-bright: #373a33; --md-sys-color-surface-container-lowest: #0c0f0a; --md-sys-color-surface-container-low: #191d16; --md-sys-color-surface-container: #1d211a; --md-sys-color-surface-container-high: #282b24; --md-sys-color-surface-container-highest: #32362f; --md-sys-color-on-surface: #e1e4d9; --md-sys-color-surface-variant: #43483e; --md-sys-color-on-surface-variant: #c3c8bb; --md-sys-color-inverse-surface: #e1e4d9; --md-sys-color-inverse-on-surface: #2e312b; --md-sys-color-outline: #8d9286; --md-sys-color-outline-variant: #43483e; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #a9d291; --md-sys-color-success: #9cd67e; --md-sys-color-on-success: #113800; --md-sys-color-success-container: #205107; --md-sys-color-on-success-container: #b8f397; --md-sys-color-warning: #e2c54a; --md-sys-color-on-warning: #3a3000; --md-sys-color-warning-container: #544600; --md-sys-color-on-warning-container: #ffe16a; }
@media (prefers-color-scheme: dark) { :root[data-palette="green"]:not([data-mode="light"]) { --md-sys-color-primary: #a9d291; --md-sys-color-on-primary: #173807; --md-sys-color-primary-container: #2d4f1c; --md-sys-color-on-primary-container: #c5efab; --md-sys-color-inverse-primary: #446732; --md-sys-color-secondary: #bdcbb0; --md-sys-color-on-secondary: #283420; --md-sys-color-secondary-container: #3e4a35; --md-sys-color-on-secondary-container: #d8e7cb; --md-sys-color-tertiary: #a0cfd0; --md-sys-color-on-tertiary: #003738; --md-sys-color-tertiary-container: #1e4e4f; --md-sys-color-on-tertiary-container: #bbebec; --md-sys-color-error: #ffb4ab; --md-sys-color-on-error: #690005; --md-sys-color-error-container: #93000a; --md-sys-color-on-error-container: #ffdad6; --md-sys-color-background: #11140f; --md-sys-color-on-background: #e1e4d9; --md-sys-color-surface: #11140f; --md-sys-color-surface-dim: #11140f; --md-sys-color-surface-bright: #373a33; --md-sys-color-surface-container-lowest: #0c0f0a; --md-sys-color-surface-container-low: #191d16; --md-sys-color-surface-container: #1d211a; --md-sys-color-surface-container-high: #282b24; --md-sys-color-surface-container-highest: #32362f; --md-sys-color-on-surface: #e1e4d9; --md-sys-color-surface-variant: #43483e; --md-sys-color-on-surface-variant: #c3c8bb; --md-sys-color-inverse-surface: #e1e4d9; --md-sys-color-inverse-on-surface: #2e312b; --md-sys-color-outline: #8d9286; --md-sys-color-outline-variant: #43483e; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #a9d291; --md-sys-color-success: #9cd67e; --md-sys-color-on-success: #113800; --md-sys-color-success-container: #205107; --md-sys-color-on-success-container: #b8f397; --md-sys-color-warning: #e2c54a; --md-sys-color-on-warning: #3a3000; --md-sys-color-warning-container: #544600; --md-sys-color-on-warning-container: #ffe16a; } } @media (prefers-color-scheme: dark) { :root[data-palette="green"]:not([data-mode="light"]) { --md-sys-color-primary: #a9d291; --md-sys-color-on-primary: #173807; --md-sys-color-primary-container: #2d4f1c; --md-sys-color-on-primary-container: #c5efab; --md-sys-color-inverse-primary: #446732; --md-sys-color-secondary: #bdcbb0; --md-sys-color-on-secondary: #283420; --md-sys-color-secondary-container: #3e4a35; --md-sys-color-on-secondary-container: #d8e7cb; --md-sys-color-tertiary: #a0cfd0; --md-sys-color-on-tertiary: #003738; --md-sys-color-tertiary-container: #1e4e4f; --md-sys-color-on-tertiary-container: #bbebec; --md-sys-color-error: #ffb4ab; --md-sys-color-on-error: #690005; --md-sys-color-error-container: #93000a; --md-sys-color-on-error-container: #ffdad6; --md-sys-color-background: #11140f; --md-sys-color-on-background: #e1e4d9; --md-sys-color-surface: #11140f; --md-sys-color-surface-dim: #11140f; --md-sys-color-surface-bright: #373a33; --md-sys-color-surface-container-lowest: #0c0f0a; --md-sys-color-surface-container-low: #191d16; --md-sys-color-surface-container: #1d211a; --md-sys-color-surface-container-high: #282b24; --md-sys-color-surface-container-highest: #32362f; --md-sys-color-on-surface: #e1e4d9; --md-sys-color-surface-variant: #43483e; --md-sys-color-on-surface-variant: #c3c8bb; --md-sys-color-inverse-surface: #e1e4d9; --md-sys-color-inverse-on-surface: #2e312b; --md-sys-color-outline: #8d9286; --md-sys-color-outline-variant: #43483e; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #a9d291; --md-sys-color-success: #9cd67e; --md-sys-color-on-success: #113800; --md-sys-color-success-container: #205107; --md-sys-color-on-success-container: #b8f397; --md-sys-color-warning: #e2c54a; --md-sys-color-on-warning: #3a3000; --md-sys-color-warning-container: #544600; --md-sys-color-on-warning-container: #ffe16a; } }
:root[data-palette="green"][data-contrast="medium"] { --md-sys-color-primary: #1d3e0d; --md-sys-color-on-primary: #ffffff; --md-sys-color-primary-container: #52773f; --md-sys-color-on-primary-container: #ffffff; --md-sys-color-inverse-primary: #a9d291; --md-sys-color-secondary: #2d3a26; --md-sys-color-on-secondary: #ffffff; --md-sys-color-secondary-container: #64715a; --md-sys-color-on-secondary-container: #ffffff; --md-sys-color-tertiary: #073d3e; --md-sys-color-on-tertiary: #ffffff; --md-sys-color-tertiary-container: #477575; --md-sys-color-on-tertiary-container: #ffffff; --md-sys-color-error: #740006; --md-sys-color-on-error: #ffffff; --md-sys-color-error-container: #cf2c27; --md-sys-color-on-error-container: #ffffff; --md-sys-color-background: #f8faf0; --md-sys-color-on-background: #191d16; --md-sys-color-surface: #f8faf0; --md-sys-color-surface-dim: #c5c8be; --md-sys-color-surface-bright: #f8faf0; --md-sys-color-surface-container-lowest: #ffffff; --md-sys-color-surface-container-low: #f2f5ea; --md-sys-color-surface-container: #e7e9df; --md-sys-color-surface-container-high: #dbded4; --md-sys-color-surface-container-highest: #d0d3c9; --md-sys-color-on-surface: #0f120c; --md-sys-color-surface-variant: #dfe4d7; --md-sys-color-on-surface-variant: #33382e; --md-sys-color-inverse-surface: #2e312b; --md-sys-color-inverse-on-surface: #f0f2e7; --md-sys-color-outline: #4f544a; --md-sys-color-outline-variant: #6a6f64; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #446732; --md-sys-color-success: #386a1f; --md-sys-color-on-success: #ffffff; --md-sys-color-success-container: #b8f397; --md-sys-color-on-success-container: #072100; --md-sys-color-warning: #6f5d00; --md-sys-color-on-warning: #ffffff; --md-sys-color-warning-container: #ffe16a; --md-sys-color-on-warning-container: #221b00; }
:root[data-palette="green"][data-contrast="medium"][data-mode="dark"] { --md-sys-color-primary: #bfe8a5; --md-sys-color-on-primary: #0b2c00; --md-sys-color-primary-container: #759b60; --md-sys-color-on-primary-container: #000000; --md-sys-color-inverse-primary: #2e501e; --md-sys-color-secondary: #d2e1c5; --md-sys-color-on-secondary: #1d2916; --md-sys-color-secondary-container: #87957c; --md-sys-color-on-secondary-container: #000000; --md-sys-color-tertiary: #b5e5e6; --md-sys-color-on-tertiary: #002b2c; --md-sys-color-tertiary-container: #6b9999; --md-sys-color-on-tertiary-container: #000000; --md-sys-color-error: #ffd2cc; --md-sys-color-on-error: #540003; --md-sys-color-error-container: #ff5449; --md-sys-color-on-error-container: #000000; --md-sys-color-background: #11140f; --md-sys-color-on-background: #e1e4d9; --md-sys-color-surface: #11140f; --md-sys-color-surface-dim: #11140f; --md-sys-color-surface-bright: #42463e; --md-sys-color-surface-container-lowest: #060804; --md-sys-color-surface-container-low: #1b1f18; --md-sys-color-surface-container: #252922; --md-sys-color-surface-container-high: #30342d; --md-sys-color-surface-container-highest: #3b3f38; --md-sys-color-on-surface: #ffffff; --md-sys-color-surface-variant: #43483e; --md-sys-color-on-surface-variant: #d9ded1; --md-sys-color-inverse-surface: #e1e4d9; --md-sys-color-inverse-on-surface: #282b24; --md-sys-color-outline: #afb4a7; --md-sys-color-outline-variant: #8d9286; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #a9d291; --md-sys-color-success: #9cd67e; --md-sys-color-on-success: #113800; --md-sys-color-success-container: #205107; --md-sys-color-on-success-container: #b8f397; --md-sys-color-warning: #e2c54a; --md-sys-color-on-warning: #3a3000; --md-sys-color-warning-container: #544600; --md-sys-color-on-warning-container: #ffe16a; }
@media (prefers-color-scheme: dark) { :root[data-palette="green"][data-contrast="medium"]:not([data-mode="light"]) { --md-sys-color-primary: #bfe8a5; --md-sys-color-on-primary: #0b2c00; --md-sys-color-primary-container: #759b60; --md-sys-color-on-primary-container: #000000; --md-sys-color-inverse-primary: #2e501e; --md-sys-color-secondary: #d2e1c5; --md-sys-color-on-secondary: #1d2916; --md-sys-color-secondary-container: #87957c; --md-sys-color-on-secondary-container: #000000; --md-sys-color-tertiary: #b5e5e6; --md-sys-color-on-tertiary: #002b2c; --md-sys-color-tertiary-container: #6b9999; --md-sys-color-on-tertiary-container: #000000; --md-sys-color-error: #ffd2cc; --md-sys-color-on-error: #540003; --md-sys-color-error-container: #ff5449; --md-sys-color-on-error-container: #000000; --md-sys-color-background: #11140f; --md-sys-color-on-background: #e1e4d9; --md-sys-color-surface: #11140f; --md-sys-color-surface-dim: #11140f; --md-sys-color-surface-bright: #42463e; --md-sys-color-surface-container-lowest: #060804; --md-sys-color-surface-container-low: #1b1f18; --md-sys-color-surface-container: #252922; --md-sys-color-surface-container-high: #30342d; --md-sys-color-surface-container-highest: #3b3f38; --md-sys-color-on-surface: #ffffff; --md-sys-color-surface-variant: #43483e; --md-sys-color-on-surface-variant: #d9ded1; --md-sys-color-inverse-surface: #e1e4d9; --md-sys-color-inverse-on-surface: #282b24; --md-sys-color-outline: #afb4a7; --md-sys-color-outline-variant: #8d9286; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #a9d291; --md-sys-color-success: #9cd67e; --md-sys-color-on-success: #113800; --md-sys-color-success-container: #205107; --md-sys-color-on-success-container: #b8f397; --md-sys-color-warning: #e2c54a; --md-sys-color-on-warning: #3a3000; --md-sys-color-warning-container: #544600; --md-sys-color-on-warning-container: #ffe16a; } }
:root[data-palette="green"][data-contrast="high"] { --md-sys-color-primary: #123304; --md-sys-color-on-primary: #ffffff; --md-sys-color-primary-container: #2f511f; --md-sys-color-on-primary-container: #ffffff; --md-sys-color-inverse-primary: #a9d291; --md-sys-color-secondary: #232f1c; --md-sys-color-on-secondary: #ffffff; --md-sys-color-secondary-container: #404d38; --md-sys-color-on-secondary-container: #ffffff; --md-sys-color-tertiary: #003233; --md-sys-color-on-tertiary: #ffffff; --md-sys-color-tertiary-container: #215051; --md-sys-color-on-tertiary-container: #ffffff; --md-sys-color-error: #600004; --md-sys-color-on-error: #ffffff; --md-sys-color-error-container: #98000a; --md-sys-color-on-error-container: #ffffff; --md-sys-color-background: #f8faf0; --md-sys-color-on-background: #191d16; --md-sys-color-surface: #f8faf0; --md-sys-color-surface-dim: #b7bab0; --md-sys-color-surface-bright: #f8faf0; --md-sys-color-surface-container-lowest: #ffffff; --md-sys-color-surface-container-low: #f0f2e7; --md-sys-color-surface-container: #e1e4d9; --md-sys-color-surface-container-high: #d3d5cb; --md-sys-color-surface-container-highest: #c5c8be; --md-sys-color-on-surface: #000000; --md-sys-color-surface-variant: #dfe4d7; --md-sys-color-on-surface-variant: #000000; --md-sys-color-inverse-surface: #2e312b; --md-sys-color-inverse-on-surface: #ffffff; --md-sys-color-outline: #292e25; --md-sys-color-outline-variant: #464b41; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #446732; --md-sys-color-success: #386a1f; --md-sys-color-on-success: #ffffff; --md-sys-color-success-container: #b8f397; --md-sys-color-on-success-container: #072100; --md-sys-color-warning: #6f5d00; --md-sys-color-on-warning: #ffffff; --md-sys-color-warning-container: #ffe16a; --md-sys-color-on-warning-container: #221b00; }
:root[data-palette="green"][data-contrast="high"][data-mode="dark"] { --md-sys-color-primary: #d2fcb8; --md-sys-color-on-primary: #000000; --md-sys-color-primary-container: #a6ce8e; --md-sys-color-on-primary-container: #020e00; --md-sys-color-inverse-primary: #2e501e; --md-sys-color-secondary: #e6f5d8; --md-sys-color-on-secondary: #000000; --md-sys-color-secondary-container: #b9c7ac; --md-sys-color-on-secondary-container: #040e02; --md-sys-color-tertiary: #c9f9f9; --md-sys-color-on-tertiary: #000000; --md-sys-color-tertiary-container: #9ccbcc; --md-sys-color-on-tertiary-container: #000e0e; --md-sys-color-error: #ffece9; --md-sys-color-on-error: #000000; --md-sys-color-error-container: #ffaea4; --md-sys-color-on-error-container: #220001; --md-sys-color-background: #11140f; --md-sys-color-on-background: #e1e4d9; --md-sys-color-surface: #11140f; --md-sys-color-surface-dim: #11140f; --md-sys-color-surface-bright: #4e514a; --md-sys-color-surface-container-lowest: #000000; --md-sys-color-surface-container-low: #1d211a; --md-sys-color-surface-container: #2e312b; --md-sys-color-surface-container-high: #393c35; --md-sys-color-surface-container-highest: #444840; --md-sys-color-on-surface: #ffffff; --md-sys-color-surface-variant: #43483e; --md-sys-color-on-surface-variant: #ffffff; --md-sys-color-inverse-surface: #e1e4d9; --md-sys-color-inverse-on-surface: #000000; --md-sys-color-outline: #edf2e4; --md-sys-color-outline-variant: #bfc4b7; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #a9d291; --md-sys-color-success: #9cd67e; --md-sys-color-on-success: #113800; --md-sys-color-success-container: #205107; --md-sys-color-on-success-container: #b8f397; --md-sys-color-warning: #e2c54a; --md-sys-color-on-warning: #3a3000; --md-sys-color-warning-container: #544600; --md-sys-color-on-warning-container: #ffe16a; }
@media (prefers-color-scheme: dark) { :root[data-palette="green"][data-contrast="high"]:not([data-mode="light"]) { --md-sys-color-primary: #d2fcb8; --md-sys-color-on-primary: #000000; --md-sys-color-primary-container: #a6ce8e; --md-sys-color-on-primary-container: #020e00; --md-sys-color-inverse-primary: #2e501e; --md-sys-color-secondary: #e6f5d8; --md-sys-color-on-secondary: #000000; --md-sys-color-secondary-container: #b9c7ac; --md-sys-color-on-secondary-container: #040e02; --md-sys-color-tertiary: #c9f9f9; --md-sys-color-on-tertiary: #000000; --md-sys-color-tertiary-container: #9ccbcc; --md-sys-color-on-tertiary-container: #000e0e; --md-sys-color-error: #ffece9; --md-sys-color-on-error: #000000; --md-sys-color-error-container: #ffaea4; --md-sys-color-on-error-container: #220001; --md-sys-color-background: #11140f; --md-sys-color-on-background: #e1e4d9; --md-sys-color-surface: #11140f; --md-sys-color-surface-dim: #11140f; --md-sys-color-surface-bright: #4e514a; --md-sys-color-surface-container-lowest: #000000; --md-sys-color-surface-container-low: #1d211a; --md-sys-color-surface-container: #2e312b; --md-sys-color-surface-container-high: #393c35; --md-sys-color-surface-container-highest: #444840; --md-sys-color-on-surface: #ffffff; --md-sys-color-surface-variant: #43483e; --md-sys-color-on-surface-variant: #ffffff; --md-sys-color-inverse-surface: #e1e4d9; --md-sys-color-inverse-on-surface: #000000; --md-sys-color-outline: #edf2e4; --md-sys-color-outline-variant: #bfc4b7; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #a9d291; --md-sys-color-success: #9cd67e; --md-sys-color-on-success: #113800; --md-sys-color-success-container: #205107; --md-sys-color-on-success-container: #b8f397; --md-sys-color-warning: #e2c54a; --md-sys-color-on-warning: #3a3000; --md-sys-color-warning-container: #544600; --md-sys-color-on-warning-container: #ffe16a; } }
/* orange (seed #8B5000) */ /* orange (seed #8B5000) */
:root[data-palette="orange"] { --md-sys-color-primary: #865319; --md-sys-color-on-primary: #ffffff; --md-sys-color-primary-container: #ffdcbe; --md-sys-color-on-primary-container: #6a3c01; --md-sys-color-inverse-primary: #fdb975; --md-sys-color-secondary: #735a42; --md-sys-color-on-secondary: #ffffff; --md-sys-color-secondary-container: #ffdcbe; --md-sys-color-on-secondary-container: #59422d; --md-sys-color-tertiary: #586339; --md-sys-color-on-tertiary: #ffffff; --md-sys-color-tertiary-container: #dce8b4; --md-sys-color-on-tertiary-container: #414b24; --md-sys-color-error: #ba1a1a; --md-sys-color-on-error: #ffffff; --md-sys-color-error-container: #ffdad6; --md-sys-color-on-error-container: #93000a; --md-sys-color-background: #fff8f5; --md-sys-color-on-background: #211a14; --md-sys-color-surface: #fff8f5; --md-sys-color-surface-dim: #e6d7cd; --md-sys-color-surface-bright: #fff8f5; --md-sys-color-surface-container-lowest: #ffffff; --md-sys-color-surface-container-low: #fff1e7; --md-sys-color-surface-container: #faebe0; --md-sys-color-surface-container-high: #f4e5db; --md-sys-color-surface-container-highest: #efe0d5; --md-sys-color-on-surface: #211a14; --md-sys-color-surface-variant: #f2dfd1; --md-sys-color-on-surface-variant: #51453a; --md-sys-color-inverse-surface: #372f28; --md-sys-color-inverse-on-surface: #fdeee3; --md-sys-color-outline: #837468; --md-sys-color-outline-variant: #d5c3b5; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #865319; --md-sys-color-success: #516600; --md-sys-color-on-success: #ffffff; --md-sys-color-success-container: #d3ee80; --md-sys-color-on-success-container: #161e00; --md-sys-color-warning: #835500; --md-sys-color-on-warning: #ffffff; --md-sys-color-warning-container: #ffddb4; --md-sys-color-on-warning-container: #291800; } :root[data-palette="orange"] { --md-sys-color-primary: #865319; --md-sys-color-on-primary: #ffffff; --md-sys-color-primary-container: #ffdcbe; --md-sys-color-on-primary-container: #6a3c01; --md-sys-color-inverse-primary: #fdb975; --md-sys-color-secondary: #735a42; --md-sys-color-on-secondary: #ffffff; --md-sys-color-secondary-container: #ffdcbe; --md-sys-color-on-secondary-container: #59422d; --md-sys-color-tertiary: #586339; --md-sys-color-on-tertiary: #ffffff; --md-sys-color-tertiary-container: #dce8b4; --md-sys-color-on-tertiary-container: #414b24; --md-sys-color-error: #ba1a1a; --md-sys-color-on-error: #ffffff; --md-sys-color-error-container: #ffdad6; --md-sys-color-on-error-container: #93000a; --md-sys-color-background: #fff8f5; --md-sys-color-on-background: #211a14; --md-sys-color-surface: #fff8f5; --md-sys-color-surface-dim: #e6d7cd; --md-sys-color-surface-bright: #fff8f5; --md-sys-color-surface-container-lowest: #ffffff; --md-sys-color-surface-container-low: #fff1e7; --md-sys-color-surface-container: #faebe0; --md-sys-color-surface-container-high: #f4e5db; --md-sys-color-surface-container-highest: #efe0d5; --md-sys-color-on-surface: #211a14; --md-sys-color-surface-variant: #f2dfd1; --md-sys-color-on-surface-variant: #51453a; --md-sys-color-inverse-surface: #372f28; --md-sys-color-inverse-on-surface: #fdeee3; --md-sys-color-outline: #837468; --md-sys-color-outline-variant: #d5c3b5; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #865319; --md-sys-color-success: #516600; --md-sys-color-on-success: #ffffff; --md-sys-color-success-container: #d3ee80; --md-sys-color-on-success-container: #161e00; --md-sys-color-warning: #835500; --md-sys-color-on-warning: #ffffff; --md-sys-color-warning-container: #ffddb4; --md-sys-color-on-warning-container: #291800; }
:root[data-palette="orange"][data-mode="dark"] { --md-sys-color-primary: #fdb975; --md-sys-color-on-primary: #4a2800; --md-sys-color-primary-container: #6a3c01; --md-sys-color-on-primary-container: #ffdcbe; --md-sys-color-inverse-primary: #865319; --md-sys-color-secondary: #e1c1a4; --md-sys-color-on-secondary: #402c18; --md-sys-color-secondary-container: #59422d; --md-sys-color-on-secondary-container: #ffdcbe; --md-sys-color-tertiary: #c0cc9a; --md-sys-color-on-tertiary: #2b3410; --md-sys-color-tertiary-container: #414b24; --md-sys-color-on-tertiary-container: #dce8b4; --md-sys-color-error: #ffb4ab; --md-sys-color-on-error: #690005; --md-sys-color-error-container: #93000a; --md-sys-color-on-error-container: #ffdad6; --md-sys-color-background: #19120c; --md-sys-color-on-background: #efe0d5; --md-sys-color-surface: #19120c; --md-sys-color-surface-dim: #19120c; --md-sys-color-surface-bright: #403830; --md-sys-color-surface-container-lowest: #130d07; --md-sys-color-surface-container-low: #211a14; --md-sys-color-surface-container: #261e18; --md-sys-color-surface-container-high: #302822; --md-sys-color-surface-container-highest: #3c332c; --md-sys-color-on-surface: #efe0d5; --md-sys-color-surface-variant: #51453a; --md-sys-color-on-surface-variant: #d5c3b5; --md-sys-color-inverse-surface: #efe0d5; --md-sys-color-inverse-on-surface: #372f28; --md-sys-color-outline: #9e8e81; --md-sys-color-outline-variant: #51453a; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #fdb975; --md-sys-color-success: #b7d167; --md-sys-color-on-success: #293500; --md-sys-color-success-container: #3c4d00; --md-sys-color-on-success-container: #d3ee80; --md-sys-color-warning: #ffb955; --md-sys-color-on-warning: #452b00; --md-sys-color-warning-container: #633f00; --md-sys-color-on-warning-container: #ffddb4; } :root[data-palette="orange"][data-mode="dark"] { --md-sys-color-primary: #fdb975; --md-sys-color-on-primary: #4a2800; --md-sys-color-primary-container: #6a3c01; --md-sys-color-on-primary-container: #ffdcbe; --md-sys-color-inverse-primary: #865319; --md-sys-color-secondary: #e1c1a4; --md-sys-color-on-secondary: #402c18; --md-sys-color-secondary-container: #59422d; --md-sys-color-on-secondary-container: #ffdcbe; --md-sys-color-tertiary: #c0cc9a; --md-sys-color-on-tertiary: #2b3410; --md-sys-color-tertiary-container: #414b24; --md-sys-color-on-tertiary-container: #dce8b4; --md-sys-color-error: #ffb4ab; --md-sys-color-on-error: #690005; --md-sys-color-error-container: #93000a; --md-sys-color-on-error-container: #ffdad6; --md-sys-color-background: #19120c; --md-sys-color-on-background: #efe0d5; --md-sys-color-surface: #19120c; --md-sys-color-surface-dim: #19120c; --md-sys-color-surface-bright: #403830; --md-sys-color-surface-container-lowest: #130d07; --md-sys-color-surface-container-low: #211a14; --md-sys-color-surface-container: #261e18; --md-sys-color-surface-container-high: #302822; --md-sys-color-surface-container-highest: #3c332c; --md-sys-color-on-surface: #efe0d5; --md-sys-color-surface-variant: #51453a; --md-sys-color-on-surface-variant: #d5c3b5; --md-sys-color-inverse-surface: #efe0d5; --md-sys-color-inverse-on-surface: #372f28; --md-sys-color-outline: #9e8e81; --md-sys-color-outline-variant: #51453a; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #fdb975; --md-sys-color-success: #b7d167; --md-sys-color-on-success: #293500; --md-sys-color-success-container: #3c4d00; --md-sys-color-on-success-container: #d3ee80; --md-sys-color-warning: #ffb955; --md-sys-color-on-warning: #452b00; --md-sys-color-warning-container: #633f00; --md-sys-color-on-warning-container: #ffddb4; }
@media (prefers-color-scheme: dark) { :root[data-palette="orange"]:not([data-mode="light"]) { --md-sys-color-primary: #fdb975; --md-sys-color-on-primary: #4a2800; --md-sys-color-primary-container: #6a3c01; --md-sys-color-on-primary-container: #ffdcbe; --md-sys-color-inverse-primary: #865319; --md-sys-color-secondary: #e1c1a4; --md-sys-color-on-secondary: #402c18; --md-sys-color-secondary-container: #59422d; --md-sys-color-on-secondary-container: #ffdcbe; --md-sys-color-tertiary: #c0cc9a; --md-sys-color-on-tertiary: #2b3410; --md-sys-color-tertiary-container: #414b24; --md-sys-color-on-tertiary-container: #dce8b4; --md-sys-color-error: #ffb4ab; --md-sys-color-on-error: #690005; --md-sys-color-error-container: #93000a; --md-sys-color-on-error-container: #ffdad6; --md-sys-color-background: #19120c; --md-sys-color-on-background: #efe0d5; --md-sys-color-surface: #19120c; --md-sys-color-surface-dim: #19120c; --md-sys-color-surface-bright: #403830; --md-sys-color-surface-container-lowest: #130d07; --md-sys-color-surface-container-low: #211a14; --md-sys-color-surface-container: #261e18; --md-sys-color-surface-container-high: #302822; --md-sys-color-surface-container-highest: #3c332c; --md-sys-color-on-surface: #efe0d5; --md-sys-color-surface-variant: #51453a; --md-sys-color-on-surface-variant: #d5c3b5; --md-sys-color-inverse-surface: #efe0d5; --md-sys-color-inverse-on-surface: #372f28; --md-sys-color-outline: #9e8e81; --md-sys-color-outline-variant: #51453a; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #fdb975; --md-sys-color-success: #b7d167; --md-sys-color-on-success: #293500; --md-sys-color-success-container: #3c4d00; --md-sys-color-on-success-container: #d3ee80; --md-sys-color-warning: #ffb955; --md-sys-color-on-warning: #452b00; --md-sys-color-warning-container: #633f00; --md-sys-color-on-warning-container: #ffddb4; } } @media (prefers-color-scheme: dark) { :root[data-palette="orange"]:not([data-mode="light"]) { --md-sys-color-primary: #fdb975; --md-sys-color-on-primary: #4a2800; --md-sys-color-primary-container: #6a3c01; --md-sys-color-on-primary-container: #ffdcbe; --md-sys-color-inverse-primary: #865319; --md-sys-color-secondary: #e1c1a4; --md-sys-color-on-secondary: #402c18; --md-sys-color-secondary-container: #59422d; --md-sys-color-on-secondary-container: #ffdcbe; --md-sys-color-tertiary: #c0cc9a; --md-sys-color-on-tertiary: #2b3410; --md-sys-color-tertiary-container: #414b24; --md-sys-color-on-tertiary-container: #dce8b4; --md-sys-color-error: #ffb4ab; --md-sys-color-on-error: #690005; --md-sys-color-error-container: #93000a; --md-sys-color-on-error-container: #ffdad6; --md-sys-color-background: #19120c; --md-sys-color-on-background: #efe0d5; --md-sys-color-surface: #19120c; --md-sys-color-surface-dim: #19120c; --md-sys-color-surface-bright: #403830; --md-sys-color-surface-container-lowest: #130d07; --md-sys-color-surface-container-low: #211a14; --md-sys-color-surface-container: #261e18; --md-sys-color-surface-container-high: #302822; --md-sys-color-surface-container-highest: #3c332c; --md-sys-color-on-surface: #efe0d5; --md-sys-color-surface-variant: #51453a; --md-sys-color-on-surface-variant: #d5c3b5; --md-sys-color-inverse-surface: #efe0d5; --md-sys-color-inverse-on-surface: #372f28; --md-sys-color-outline: #9e8e81; --md-sys-color-outline-variant: #51453a; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #fdb975; --md-sys-color-success: #b7d167; --md-sys-color-on-success: #293500; --md-sys-color-success-container: #3c4d00; --md-sys-color-on-success-container: #d3ee80; --md-sys-color-warning: #ffb955; --md-sys-color-on-warning: #452b00; --md-sys-color-warning-container: #633f00; --md-sys-color-on-warning-container: #ffddb4; } }
:root[data-palette="orange"][data-contrast="medium"] { --md-sys-color-primary: #522d00; --md-sys-color-on-primary: #ffffff; --md-sys-color-primary-container: #976126; --md-sys-color-on-primary-container: #ffffff; --md-sys-color-inverse-primary: #fdb975; --md-sys-color-secondary: #47321d; --md-sys-color-on-secondary: #ffffff; --md-sys-color-secondary-container: #826850; --md-sys-color-on-secondary-container: #ffffff; --md-sys-color-tertiary: #313a15; --md-sys-color-on-tertiary: #ffffff; --md-sys-color-tertiary-container: #677247; --md-sys-color-on-tertiary-container: #ffffff; --md-sys-color-error: #740006; --md-sys-color-on-error: #ffffff; --md-sys-color-error-container: #cf2c27; --md-sys-color-on-error-container: #ffffff; --md-sys-color-background: #fff8f5; --md-sys-color-on-background: #211a14; --md-sys-color-surface: #fff8f5; --md-sys-color-surface-dim: #d2c4ba; --md-sys-color-surface-bright: #fff8f5; --md-sys-color-surface-container-lowest: #ffffff; --md-sys-color-surface-container-low: #fff1e7; --md-sys-color-surface-container: #f4e5db; --md-sys-color-surface-container-high: #e9dad0; --md-sys-color-surface-container-highest: #ddcfc5; --md-sys-color-on-surface: #16100a; --md-sys-color-surface-variant: #f2dfd1; --md-sys-color-on-surface-variant: #3f342a; --md-sys-color-inverse-surface: #372f28; --md-sys-color-inverse-on-surface: #fdeee3; --md-sys-color-outline: #5d5045; --md-sys-color-outline-variant: #796a5f; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #865319; --md-sys-color-success: #516600; --md-sys-color-on-success: #ffffff; --md-sys-color-success-container: #d3ee80; --md-sys-color-on-success-container: #161e00; --md-sys-color-warning: #835500; --md-sys-color-on-warning: #ffffff; --md-sys-color-warning-container: #ffddb4; --md-sys-color-on-warning-container: #291800; }
:root[data-palette="orange"][data-contrast="medium"][data-mode="dark"] { --md-sys-color-primary: #ffd5af; --md-sys-color-on-primary: #3b1f00; --md-sys-color-primary-container: #c08446; --md-sys-color-on-primary-container: #000000; --md-sys-color-inverse-primary: #6b3d02; --md-sys-color-secondary: #f8d6b9; --md-sys-color-on-secondary: #34220e; --md-sys-color-secondary-container: #a88b71; --md-sys-color-on-secondary-container: #000000; --md-sys-color-tertiary: #d6e2ae; --md-sys-color-on-tertiary: #202906; --md-sys-color-tertiary-container: #8b9668; --md-sys-color-on-tertiary-container: #000000; --md-sys-color-error: #ffd2cc; --md-sys-color-on-error: #540003; --md-sys-color-error-container: #ff5449; --md-sys-color-on-error-container: #000000; --md-sys-color-background: #19120c; --md-sys-color-on-background: #efe0d5; --md-sys-color-surface: #19120c; --md-sys-color-surface-dim: #19120c; --md-sys-color-surface-bright: #4c433b; --md-sys-color-surface-container-lowest: #0c0603; --md-sys-color-surface-container-low: #241c16; --md-sys-color-surface-container: #2e2620; --md-sys-color-surface-container-high: #39312a; --md-sys-color-surface-container-highest: #453c34; --md-sys-color-on-surface: #ffffff; --md-sys-color-surface-variant: #51453a; --md-sys-color-on-surface-variant: #ecd9cb; --md-sys-color-inverse-surface: #efe0d5; --md-sys-color-inverse-on-surface: #312922; --md-sys-color-outline: #c0afa1; --md-sys-color-outline-variant: #9d8d81; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #fdb975; --md-sys-color-success: #b7d167; --md-sys-color-on-success: #293500; --md-sys-color-success-container: #3c4d00; --md-sys-color-on-success-container: #d3ee80; --md-sys-color-warning: #ffb955; --md-sys-color-on-warning: #452b00; --md-sys-color-warning-container: #633f00; --md-sys-color-on-warning-container: #ffddb4; }
@media (prefers-color-scheme: dark) { :root[data-palette="orange"][data-contrast="medium"]:not([data-mode="light"]) { --md-sys-color-primary: #ffd5af; --md-sys-color-on-primary: #3b1f00; --md-sys-color-primary-container: #c08446; --md-sys-color-on-primary-container: #000000; --md-sys-color-inverse-primary: #6b3d02; --md-sys-color-secondary: #f8d6b9; --md-sys-color-on-secondary: #34220e; --md-sys-color-secondary-container: #a88b71; --md-sys-color-on-secondary-container: #000000; --md-sys-color-tertiary: #d6e2ae; --md-sys-color-on-tertiary: #202906; --md-sys-color-tertiary-container: #8b9668; --md-sys-color-on-tertiary-container: #000000; --md-sys-color-error: #ffd2cc; --md-sys-color-on-error: #540003; --md-sys-color-error-container: #ff5449; --md-sys-color-on-error-container: #000000; --md-sys-color-background: #19120c; --md-sys-color-on-background: #efe0d5; --md-sys-color-surface: #19120c; --md-sys-color-surface-dim: #19120c; --md-sys-color-surface-bright: #4c433b; --md-sys-color-surface-container-lowest: #0c0603; --md-sys-color-surface-container-low: #241c16; --md-sys-color-surface-container: #2e2620; --md-sys-color-surface-container-high: #39312a; --md-sys-color-surface-container-highest: #453c34; --md-sys-color-on-surface: #ffffff; --md-sys-color-surface-variant: #51453a; --md-sys-color-on-surface-variant: #ecd9cb; --md-sys-color-inverse-surface: #efe0d5; --md-sys-color-inverse-on-surface: #312922; --md-sys-color-outline: #c0afa1; --md-sys-color-outline-variant: #9d8d81; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #fdb975; --md-sys-color-success: #b7d167; --md-sys-color-on-success: #293500; --md-sys-color-success-container: #3c4d00; --md-sys-color-on-success-container: #d3ee80; --md-sys-color-warning: #ffb955; --md-sys-color-on-warning: #452b00; --md-sys-color-warning-container: #633f00; --md-sys-color-on-warning-container: #ffddb4; } }
:root[data-palette="orange"][data-contrast="high"] { --md-sys-color-primary: #442400; --md-sys-color-on-primary: #ffffff; --md-sys-color-primary-container: #6c3e03; --md-sys-color-on-primary-container: #ffffff; --md-sys-color-inverse-primary: #fdb975; --md-sys-color-secondary: #3c2814; --md-sys-color-on-secondary: #ffffff; --md-sys-color-secondary-container: #5c452f; --md-sys-color-on-secondary-container: #ffffff; --md-sys-color-tertiary: #272f0c; --md-sys-color-on-tertiary: #ffffff; --md-sys-color-tertiary-container: #434d26; --md-sys-color-on-tertiary-container: #ffffff; --md-sys-color-error: #600004; --md-sys-color-on-error: #ffffff; --md-sys-color-error-container: #98000a; --md-sys-color-on-error-container: #ffffff; --md-sys-color-background: #fff8f5; --md-sys-color-on-background: #211a14; --md-sys-color-surface: #fff8f5; --md-sys-color-surface-dim: #c4b6ac; --md-sys-color-surface-bright: #fff8f5; --md-sys-color-surface-container-lowest: #ffffff; --md-sys-color-surface-container-low: #fdeee3; --md-sys-color-surface-container: #efe0d5; --md-sys-color-surface-container-high: #e0d2c7; --md-sys-color-surface-container-highest: #d2c4ba; --md-sys-color-on-surface: #000000; --md-sys-color-surface-variant: #f2dfd1; --md-sys-color-on-surface-variant: #000000; --md-sys-color-inverse-surface: #372f28; --md-sys-color-inverse-on-surface: #ffffff; --md-sys-color-outline: #352a21; --md-sys-color-outline-variant: #53473c; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #865319; --md-sys-color-success: #516600; --md-sys-color-on-success: #ffffff; --md-sys-color-success-container: #d3ee80; --md-sys-color-on-success-container: #161e00; --md-sys-color-warning: #835500; --md-sys-color-on-warning: #ffffff; --md-sys-color-warning-container: #ffddb4; --md-sys-color-on-warning-container: #291800; }
:root[data-palette="orange"][data-contrast="high"][data-mode="dark"] { --md-sys-color-primary: #ffeddf; --md-sys-color-on-primary: #000000; --md-sys-color-primary-container: #f9b572; --md-sys-color-on-primary-container: #160800; --md-sys-color-inverse-primary: #6b3d02; --md-sys-color-secondary: #ffeddf; --md-sys-color-on-secondary: #000000; --md-sys-color-secondary-container: #ddbda0; --md-sys-color-on-secondary-container: #160800; --md-sys-color-tertiary: #eaf6c0; --md-sys-color-on-tertiary: #000000; --md-sys-color-tertiary-container: #bcc896; --md-sys-color-on-tertiary-container: #080d00; --md-sys-color-error: #ffece9; --md-sys-color-on-error: #000000; --md-sys-color-error-container: #ffaea4; --md-sys-color-on-error-container: #220001; --md-sys-color-background: #19120c; --md-sys-color-on-background: #efe0d5; --md-sys-color-surface: #19120c; --md-sys-color-surface-dim: #19120c; --md-sys-color-surface-bright: #584e46; --md-sys-color-surface-container-lowest: #000000; --md-sys-color-surface-container-low: #261e18; --md-sys-color-surface-container: #372f28; --md-sys-color-surface-container-high: #433a32; --md-sys-color-surface-container-highest: #4e453d; --md-sys-color-on-surface: #ffffff; --md-sys-color-surface-variant: #51453a; --md-sys-color-on-surface-variant: #ffffff; --md-sys-color-inverse-surface: #efe0d5; --md-sys-color-inverse-on-surface: #000000; --md-sys-color-outline: #ffeddf; --md-sys-color-outline-variant: #d1bfb2; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #fdb975; --md-sys-color-success: #b7d167; --md-sys-color-on-success: #293500; --md-sys-color-success-container: #3c4d00; --md-sys-color-on-success-container: #d3ee80; --md-sys-color-warning: #ffb955; --md-sys-color-on-warning: #452b00; --md-sys-color-warning-container: #633f00; --md-sys-color-on-warning-container: #ffddb4; }
@media (prefers-color-scheme: dark) { :root[data-palette="orange"][data-contrast="high"]:not([data-mode="light"]) { --md-sys-color-primary: #ffeddf; --md-sys-color-on-primary: #000000; --md-sys-color-primary-container: #f9b572; --md-sys-color-on-primary-container: #160800; --md-sys-color-inverse-primary: #6b3d02; --md-sys-color-secondary: #ffeddf; --md-sys-color-on-secondary: #000000; --md-sys-color-secondary-container: #ddbda0; --md-sys-color-on-secondary-container: #160800; --md-sys-color-tertiary: #eaf6c0; --md-sys-color-on-tertiary: #000000; --md-sys-color-tertiary-container: #bcc896; --md-sys-color-on-tertiary-container: #080d00; --md-sys-color-error: #ffece9; --md-sys-color-on-error: #000000; --md-sys-color-error-container: #ffaea4; --md-sys-color-on-error-container: #220001; --md-sys-color-background: #19120c; --md-sys-color-on-background: #efe0d5; --md-sys-color-surface: #19120c; --md-sys-color-surface-dim: #19120c; --md-sys-color-surface-bright: #584e46; --md-sys-color-surface-container-lowest: #000000; --md-sys-color-surface-container-low: #261e18; --md-sys-color-surface-container: #372f28; --md-sys-color-surface-container-high: #433a32; --md-sys-color-surface-container-highest: #4e453d; --md-sys-color-on-surface: #ffffff; --md-sys-color-surface-variant: #51453a; --md-sys-color-on-surface-variant: #ffffff; --md-sys-color-inverse-surface: #efe0d5; --md-sys-color-inverse-on-surface: #000000; --md-sys-color-outline: #ffeddf; --md-sys-color-outline-variant: #d1bfb2; --md-sys-color-shadow: #000000; --md-sys-color-scrim: #000000; --md-sys-color-surface-tint: #fdb975; --md-sys-color-success: #b7d167; --md-sys-color-on-success: #293500; --md-sys-color-success-container: #3c4d00; --md-sys-color-on-success-container: #d3ee80; --md-sys-color-warning: #ffb955; --md-sys-color-on-warning: #452b00; --md-sys-color-warning-container: #633f00; --md-sys-color-on-warning-container: #ffddb4; } }
+34 -1
View File
@@ -149,7 +149,40 @@ a.issue.closed, a.issue.closed:link, a.issue.closed:visited { color: var(--md-sy
#filters-table .filter .field { display: flex; align-items: center; gap: 8px; min-block-size: 40px; } #filters-table .filter .field { display: flex; align-items: center; gap: 8px; min-block-size: 40px; }
#filters-table .filter .field input[type="checkbox"] { margin: 0; } #filters-table .filter .field input[type="checkbox"] { margin: 0; }
#filters-table .filter .values { display: flex; align-items: center; min-block-size: 40px; flex-wrap: wrap; gap: 4px 8px; } #filters-table .filter .values { display: flex; align-items: center; min-block-size: 40px; flex-wrap: wrap; gap: 4px 8px; }
.add-filter { display: flex; align-items: center; justify-content: flex-end; gap: 8px; } /* "Add filter" is an M3 tonal button, not one more outlined field. A select has no
placeholder, so the label lies over it in the same grid cell and lets taps through.
A zero width with a 100% minimum keeps the longest option from widening the button. */
.add-filter { display: grid; justify-content: end; margin-block-start: 4px; }
.add-filter > * { grid-area: 1 / 1; }
.add-filter select {
inline-size: 0;
min-inline-size: 100%;
block-size: 40px;
border: 0;
border-radius: var(--md-sys-shape-corner-full);
background: var(--md-sys-color-secondary-container); /* drops the dropdown arrow too */
cursor: pointer;
}
.add-filter select:hover { background-image: var(--nm-state-hover); }
.add-filter label {
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
padding-inline: 16px 24px;
pointer-events: none;
color: var(--md-sys-color-on-secondary-container);
font: var(--md-sys-typescale-label-large);
letter-spacing: var(--md-sys-typescale-label-large-tracking);
}
.add-filter label::before {
content: '';
inline-size: 18px;
block-size: 18px;
background-color: currentColor;
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 -960 960 960'%3E%3Cpath d='M440-440H200v-80h240v-240h80v240h240v80H520v240h-80v-240Z'/%3E%3C/svg%3E") center / 18px no-repeat;
}
/* Core fixes the operator at 120px, too narrow for "has never been" with M3 field padding */ /* Core fixes the operator at 120px, too narrow for "has never been" with M3 field padding */
#filters-table .filter .operator select { inline-size: 180px; } #filters-table .filter .operator select { inline-size: 180px; }
/* All value fields share one width so the filter rows form a clean second column. /* All value fields share one width so the filter rows form a clean second column.
+1 -1
View File
@@ -25,7 +25,7 @@ p.breadcrumb, p.subtitle, p.footnote, div.issue p.author, div.issue div.subject
#issue_tree .issues-stat, #relations .issues-stat, .wiki-update-info, p.other-formats, em.info, #issue_tree .issues-stat, #relations .issues-stat, .wiki-update-info, p.other-formats, em.info,
div.attachments span.author, table.files tr.file td.digest, tr.entry td.size, div.attachments span.author, table.files tr.file td.digest, tr.entry td.size,
table.plugins span.description, table.plugins span.url, #document-list .document-item .updated_on, table.plugins span.description, table.plugins span.url, #document-list .document-item .updated_on,
.cal .calbody, div.tooltip:hover span.tip, div.issue.details .reaction, div.message .reaction, div.news .reaction { .cal .calbody, div.issue.details .reaction, div.message .reaction, div.news .reaction {
font-size: 0.75rem; font-size: 0.75rem;
} }
div.issue span.private, div.journal span.private { font-size: 0.6875rem; } div.issue span.private, div.journal span.private { font-size: 0.6875rem; }
+25 -33
View File
@@ -1,64 +1,52 @@
// Generates tonalmine/stylesheets/palettes.css: one M3 color scheme per seed color, // Generates tonalmine/stylesheets/palettes.css: one M3 color scheme per seed color,
// in light and dark. Run: cd tools/palettes && npm install && npm run build // in light and dark. Run: cd tools/palettes && npm install && npm run build
// The same build bundles scheme.mjs into tonalmine/javascripts/scheme.js for the custom color picker
// (bundled with esbuild because material-color-utilities 0.4.0 has extensionless ESM imports). // (bundled with esbuild because material-color-utilities 0.4.0 has extensionless ESM imports).
import { writeFileSync, readFileSync } from 'node:fs'; import { writeFileSync, readFileSync } from 'node:fs';
import { resolve } from 'node:path'; import { resolve } from 'node:path';
import { Hct, SchemeTonalSpot, MaterialDynamicColors as C, hexFromArgb, argbFromHex, customColor } from '@material/material-color-utilities'; import { ROLES, kebab, paletteCss } from './scheme.mjs';
// The first palette is the default. Ids must match PALETTES in tonalmine/javascripts/theme.js. // The first palette is the default. Ids must match PALETTES in tonalmine/javascripts/theme.js
// and redmine_tonalmine/lib/redmine_tonalmine/appearance.rb.
// An optional third entry is a Material Theme Builder export folder (light.css, dark.css and the
// -mc/-hc contrast variants):
// its roles are copied as-is instead of generated, and the seed only drives success/warning.
const PALETTES = [ const PALETTES = [
['purple', '#6750A4'], ['purple', '#6750A4'],
['blue', '#0061A4'], ['blue', '#0061A4'],
['teal', '#006A6A'],
['green', '#386A20'], ['green', '#386A20'],
['orange', '#8B5000'], ['orange', '#8B5000'],
]; ];
const CUSTOM = [['success', '#386A20'], ['warning', '#7C5800']];
const ROLES = ['primary', 'onPrimary', 'primaryContainer', 'onPrimaryContainer', 'inversePrimary',
'secondary', 'onSecondary', 'secondaryContainer', 'onSecondaryContainer',
'tertiary', 'onTertiary', 'tertiaryContainer', 'onTertiaryContainer',
'error', 'onError', 'errorContainer', 'onErrorContainer',
'background', 'onBackground', 'surface', 'surfaceDim', 'surfaceBright',
'surfaceContainerLowest', 'surfaceContainerLow', 'surfaceContainer', 'surfaceContainerHigh', 'surfaceContainerHighest',
'onSurface', 'surfaceVariant', 'onSurfaceVariant', 'inverseSurface', 'inverseOnSurface',
'outline', 'outlineVariant', 'shadow', 'scrim', 'surfaceTint'];
const kebab = (s) => s.replace(/[A-Z]/g, (m) => '-' + m.toLowerCase()); // Reads the roles from a Theme Builder export; fails if one is missing.
const hex = (argb) => hexFromArgb(argb); function exported(dir, dark, contrast) {
const file = `${dir}/${dark ? 'dark' : 'light'}${{ medium: '-mc', high: '-hc' }[contrast] || ''}.css`;
function roles(seedHex, dark) { const vars = Object.fromEntries([...readFileSync(file, 'utf8').matchAll(/--md-sys-color-([a-z-]+):\s*([^;]+);/g)].map((m) => [m[1], m[2].trim()]));
const seed = argbFromHex(seedHex); return ROLES.map((r) => {
const scheme = new SchemeTonalSpot(Hct.fromInt(seed), dark, 0); if (!vars[kebab(r)]) { console.error(`${file} is missing --md-sys-color-${kebab(r)}`); process.exit(1); }
const lines = ROLES.map((r) => `--md-sys-color-${kebab(r)}: ${hex(C[r].getArgb(scheme))};`); return `--md-sys-color-${kebab(r)}: ${vars[kebab(r)]};`;
for (const [name, value] of CUSTOM) { });
const c = customColor(seed, { name, value: argbFromHex(value), blend: true })[dark ? 'dark' : 'light'];
lines.push(`--md-sys-color-${name}: ${hex(c.color)};`, `--md-sys-color-on-${name}: ${hex(c.onColor)};`,
`--md-sys-color-${name}-container: ${hex(c.colorContainer)};`, `--md-sys-color-on-${name}-container: ${hex(c.onColorContainer)};`);
}
return lines.join(' ');
} }
// Specificity: the default palette uses bare :root, the others add [data-palette], and every dark // Specificity: the default palette uses bare :root, the others add [data-palette], and every dark
// rule adds one more attribute, so "palette + mode" always beats "palette" and the default. // rule adds one more attribute, so "palette + mode" always beats "palette" and the default.
let css = `/* let css = `/*
* Generated by tools/palettes/build_palettes.mjs; do not edit by hand. * Generated by tools/palettes/build_palettes.mjs; do not edit by hand.
* M3 color roles (TonalSpot, contrast 0) per palette, light and dark. * M3 color roles (TonalSpot) per palette, light and dark, at contrast 0, 0.5 and 1.
* <html data-palette="..."> picks the palette, <html data-mode="light|dark"> forces a mode; * <html data-palette="..."> picks the palette, <html data-mode="light|dark"> forces a mode;
* without data-mode the OS setting (prefers-color-scheme) decides. * without data-mode the OS setting (prefers-color-scheme) decides.
* <html data-contrast="medium|high"> picks the contrast level; without it, standard.
*/ */
:root { color-scheme: light; } :root { color-scheme: light; }
:root[data-mode="dark"] { color-scheme: dark; } :root[data-mode="dark"] { color-scheme: dark; }
@media (prefers-color-scheme: dark) { :root:not([data-mode="light"]) { color-scheme: dark; } } @media (prefers-color-scheme: dark) { :root:not([data-mode="light"]) { color-scheme: dark; } }
`; `;
PALETTES.forEach(([id, seed], i) => { PALETTES.forEach(([id, seed, dir], i) => {
const sel = i === 0 ? ':root' : `:root[data-palette="${id}"]`; const sel = i === 0 ? ':root' : `:root[data-palette="${id}"]`;
css += ` css += `
/* ${id} (seed ${seed}) */ /* ${id} (${dir ? `export ${dir}, ` : ''}seed ${seed}) */
${sel} { ${roles(seed, false)} } ` + paletteCss(sel, seed, dir && ((dark, contrast) => exported(dir, dark, contrast)));
${sel}[data-mode="dark"] { ${roles(seed, true)} }
@media (prefers-color-scheme: dark) { ${sel}:not([data-mode="light"]) { ${roles(seed, true)} } }
`;
}); });
// Paths are relative to tools/palettes (npm runs scripts there); the bundle itself lives in .build/. // Paths are relative to tools/palettes (npm runs scripts there); the bundle itself lives in .build/.
@@ -66,8 +54,12 @@ const theme = resolve(process.cwd(), '../../tonalmine');
const out = `${theme}/stylesheets/palettes.css`; const out = `${theme}/stylesheets/palettes.css`;
writeFileSync(out, css); writeFileSync(out, css);
// Keep theme.js in sync: every palette id must appear in its PALETTES list. // Keep theme.js and the plugin's validation in sync: every palette id must appear in their PALETTES lists.
const js = readFileSync(`${theme}/javascripts/theme.js`, 'utf8'); const js = readFileSync(`${theme}/javascripts/theme.js`, 'utf8');
const missing = PALETTES.map(([id]) => id).filter((id) => !js.includes(`'${id}'`)); const missing = PALETTES.map(([id]) => id).filter((id) => !js.includes(`'${id}'`));
if (missing.length) { console.error(`theme.js is missing palettes: ${missing.join(', ')}`); process.exit(1); } if (missing.length) { console.error(`theme.js is missing palettes: ${missing.join(', ')}`); process.exit(1); }
const rb = readFileSync(resolve(theme, '../redmine_tonalmine/lib/redmine_tonalmine/appearance.rb'), 'utf8');
const rbIds = rb.match(/PALETTES = %w\[([^\]]*)\]/)[1].split(/\s+/);
const rbMissing = PALETTES.map(([id]) => id).filter((id) => !rbIds.includes(id));
if (rbMissing.length) { console.error(`appearance.rb is missing palettes: ${rbMissing.join(', ')}`); process.exit(1); }
console.log(`Wrote ${PALETTES.length} palettes to ${out}`); console.log(`Wrote ${PALETTES.length} palettes to ${out}`);
+1 -1
View File
@@ -3,7 +3,7 @@
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
"build": "esbuild build_palettes.mjs --bundle --platform=node --format=esm --outfile=.build/palettes.mjs --log-level=warning && node .build/palettes.mjs" "build": "esbuild scheme.mjs --bundle --minify --format=iife --global-name=tonalmineScheme --outfile=../../tonalmine/javascripts/scheme.js --log-level=warning && esbuild build_palettes.mjs --bundle --platform=node --format=esm --outfile=.build/palettes.mjs --log-level=warning && node .build/palettes.mjs"
}, },
"devDependencies": { "devDependencies": {
"@material/material-color-utilities": "0.4.0", "@material/material-color-utilities": "0.4.0",
+47
View File
@@ -0,0 +1,47 @@
// M3 color roles for one palette, shared by build_palettes.mjs (palettes.css) and the browser
// (bundled as tonalmine/javascripts/scheme.js, global tonalmineScheme, for the custom color picker).
import { Hct, SchemeTonalSpot, MaterialDynamicColors as C, hexFromArgb, argbFromHex, customColor } from '@material/material-color-utilities';
const CUSTOM = [['success', '#386A20'], ['warning', '#7C5800']];
export const ROLES = ['primary', 'onPrimary', 'primaryContainer', 'onPrimaryContainer', 'inversePrimary',
'secondary', 'onSecondary', 'secondaryContainer', 'onSecondaryContainer',
'tertiary', 'onTertiary', 'tertiaryContainer', 'onTertiaryContainer',
'error', 'onError', 'errorContainer', 'onErrorContainer',
'background', 'onBackground', 'surface', 'surfaceDim', 'surfaceBright',
'surfaceContainerLowest', 'surfaceContainerLow', 'surfaceContainer', 'surfaceContainerHigh', 'surfaceContainerHighest',
'onSurface', 'surfaceVariant', 'onSurfaceVariant', 'inverseSurface', 'inverseOnSurface',
'outline', 'outlineVariant', 'shadow', 'scrim', 'surfaceTint'];
export const kebab = (s) => s.replace(/[A-Z]/g, (m) => '-' + m.toLowerCase());
const hex = (argb) => hexFromArgb(argb);
// Contrast levels as Material Theme Builder has them; '' is standard (no data-contrast attribute).
export const CONTRASTS = [['', 0], ['medium', 0.5], ['high', 1]];
// TonalSpot roles plus success/warning; `own` replaces the generated ROLES lines.
// ponytail: success/warning use customColor, which has no contrast level (same tones at every level).
function roles(seedHex, dark, level, own) {
const seed = argbFromHex(seedHex);
const scheme = new SchemeTonalSpot(Hct.fromInt(seed), dark, level);
const lines = own || ROLES.map((r) => `--md-sys-color-${kebab(r)}: ${hex(C[r].getArgb(scheme))};`);
for (const [name, value] of CUSTOM) {
const c = customColor(seed, { name, value: argbFromHex(value), blend: true })[dark ? 'dark' : 'light'];
lines.push(`--md-sys-color-${name}: ${hex(c.color)};`, `--md-sys-color-on-${name}: ${hex(c.onColor)};`,
`--md-sys-color-${name}-container: ${hex(c.colorContainer)};`, `--md-sys-color-on-${name}-container: ${hex(c.onColorContainer)};`);
}
return lines.join(' ');
}
// Light, forced dark and OS dark rules for one palette selector, per contrast level.
// `own(dark, contrast)` may return role lines to use instead of generated ones.
// Each contrast rule adds an attribute, so it beats the same palette's standard rules.
export function paletteCss(sel, seedHex, own) {
return CONTRASTS.map(([id, level]) => {
const s = id ? `${sel}[data-contrast="${id}"]` : sel;
const r = (dark) => roles(seedHex, dark, level, own && own(dark, id));
return `${s} { ${r(false)} }
${s}[data-mode="dark"] { ${r(true)} }
@media (prefers-color-scheme: dark) { ${s}:not([data-mode="light"]) { ${r(true)} } }
`;
}).join('');
}