Initial commit: Tonalmine, a Material Design 3 theme for Redmine 7

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
surtic86
2026-10-01 06:05:29 +02:00
co-authored by Claude Opus 5.5
commit 06cd1db99c
47 changed files with 4302 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
node_modules/
screenshots/
.DS_Store
tools/.cache/
docs/plans/
+25
View File
@@ -0,0 +1,25 @@
MIT License
Copyright (c) 2026 nonameweb
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Bundled third-party assets keep their own licenses:
- tonalmine/fonts/ — Roboto, SIL Open Font License 1.1 (see OFL.txt there)
- tonalmine/images/icons.svg — Material Symbols, Apache License 2.0
+70
View File
@@ -0,0 +1,70 @@
# Tonalmine
Material Design 3 theme for Redmine 7.0.
## Install
1. Copy the `tonalmine/` folder into `<redmine>/themes/`.
2. Restart Redmine (theme assets are compiled at boot).
3. Select **Tonalmine** in Administration → Settings → Display → Theme.
## Development
Requires Docker.
```sh
cd dev
docker compose up -d # Redmine 7.0.1 on http://localhost:3070
docker compose exec redmine bin/rails runner /seed/seed.rb # once: demo data, selects the theme
docker compose restart redmine # after every CSS change
```
Login: `admin` / `admin`.
The theme folder is bind-mounted into the container, so edits are picked up on restart.
### Screenshots
```sh
cd e2e
npm install && npx playwright install chromium # once
node screenshots.mjs # all pages, light/dark × desktop/mobile → screenshots/
node screenshots.mjs issue # only pages whose name contains "issue"
node check-form-alignment.mjs # fails if a form label is not level with its field
node check-font-sizes.mjs # fails if any text is off the type scale (e.g. core 0.9em leftovers)
```
### Icons
`tonalmine/images/icons.svg` replaces Redmine's icon sprite with Material Symbols Outlined.
It is generated from `tools/icon_map.yml`:
```sh
ruby tools/build_icons.rb
```
The script fails when Redmine's `config/icon_source.yml` (7.0-stable) has icon names that are missing from
the map, so run it after every Redmine update.
### Colors
Each user picks a mode (Auto / Light / Dark) and a color (Purple, Blue, Teal, Green, Orange) in the
**Appearance** section of the profile menu (top right; in the mobile menu at the bottom). The choice is
stored per browser (`localStorage`); Auto follows the operating system.
The color roles are generated from one seed color per palette with `@material/material-color-utilities`
(scheme TonalSpot) into `tonalmine/stylesheets/palettes.css`:
```sh
cd tools/palettes
npm install # once
npm run build # writes palettes.css
```
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
hard-coded colors are the select chevrons `images/arrow-drop-down*.svg` (one per mode).
## License
MIT, see `LICENSE`. Roboto (OFL 1.1) and Material Symbols (Apache 2.0) keep their own licenses.
+14
View File
@@ -0,0 +1,14 @@
services:
redmine:
image: redmine:7.0.1
ports:
- "3070:3000"
volumes:
- redmine-sqlite:/usr/src/redmine/sqlite
- redmine-files:/usr/src/redmine/files
- ../tonalmine:/usr/src/redmine/themes/tonalmine
- .:/seed:ro
volumes:
redmine-sqlite:
redmine-files:
+201
View File
@@ -0,0 +1,201 @@
# Demo data for visual theme testing.
# Run: docker compose exec redmine bin/rails runner /seed/seed.rb
# Idempotent: exits if the demo project already exists. Dates are relative to today
# so Gantt, calendar and roadmap always show current data.
if Project.exists?(identifier: 'apollo')
puts 'Demo data already present.'
load File.join(__dir__, 'seed_extras.rb')
exit
end
srand(42)
today = Date.today
# One transaction: a failure leaves no partial data.
ActiveRecord::Base.transaction do
Redmine::DefaultData::Loader.load('en') if Redmine::DefaultData::Loader.no_data?
Setting.ui_theme = 'tonalmine'
Setting.text_formatting = 'common_mark'
Setting.notified_events = [] # demo data must not send mail
admin = User.find_by(login: 'admin')
admin.update!(must_change_passwd: false)
User.current = admin
people = [
%w[jdoe Jane Doe], %w[msmith Max Smith], %w[lnguyen Linh Nguyen], %w[pweber Paula Weber]
].map do |login, first, last|
User.create!(login: login, firstname: first, lastname: last, mail: "#{login}@example.com",
password: 'password123', password_confirmation: 'password123', language: 'en')
end
team = Group.create!(lastname: 'Developers')
team.users << people[0..2]
manager = Role.find_by!(name: 'Manager')
developer = Role.find_by!(name: 'Developer')
reporter = Role.find_by!(name: 'Reporter')
customer_field = IssueCustomField.create!(name: 'Customer', field_format: 'list', possible_values: %w[Acme Globex Initech],
is_for_all: true, is_filter: true, tracker_ids: Tracker.ids)
review_field = IssueCustomField.create!(name: 'Review date', field_format: 'date', is_for_all: true,
is_filter: true, tracker_ids: Tracker.ids)
modules = Redmine::AccessControl.available_project_modules.map(&:to_s)
make_project = lambda do |name, identifier, parent, description|
Project.create!(name: name, identifier: identifier, description: description, is_public: true,
parent: parent, tracker_ids: Tracker.ids, enabled_module_names: modules)
end
apollo = make_project.('Apollo', 'apollo', nil, "Customer portal relaunch.\n\n**Goal:** ship v2.0 this quarter.")
web = make_project.('Apollo Web', 'apollo-web', apollo, 'Frontend of the customer portal.')
api = make_project.('Apollo API', 'apollo-api', apollo, 'REST backend.')
infra = make_project.('Infrastructure', 'infra', nil, 'Servers, CI and monitoring.')
projects = [apollo, web, api, infra]
projects.each do |p|
Member.create!(project: p, principal: admin, role_ids: [manager.id])
Member.create!(project: p, principal: people[0], role_ids: [manager.id])
Member.create!(project: p, principal: team, role_ids: [developer.id])
Member.create!(project: p, principal: people[3], role_ids: [reporter.id])
%w[Frontend Backend Docs].each { |c| IssueCategory.create!(project: p, name: c) }
end
v1 = Version.create!(project: apollo, name: '1.0', effective_date: today - 60, sharing: 'descendants')
v2 = Version.create!(project: apollo, name: '2.0', effective_date: today + 14, sharing: 'descendants',
description: 'Portal relaunch')
v3 = Version.create!(project: apollo, name: '2.1', effective_date: today + 60, sharing: 'descendants')
vi = Version.create!(project: infra, name: 'Q4 hardening', effective_date: today + 30)
titles = [
'Login page shows wrong error message', 'Add CSV export to reports', 'Upgrade database driver',
'Dashboard loads slowly with many widgets', 'Password reset mail not localized', 'Add dark mode',
'Refactor session handling', 'Document REST authentication', 'Search ignores umlauts',
'Broken layout on small screens', 'Add audit log', 'Rate limit API endpoints', 'Improve onboarding tour',
'Fix timezone in due dates', 'Add SSO via OIDC', 'Remove legacy XML endpoints', 'Paginate project list',
'Cache avatar images', 'Nightly backup verification', 'Migrate CI to new runners'
]
open_statuses = IssueStatus.sorted.reject(&:is_closed?)
closed_status = IssueStatus.sorted.find(&:is_closed?)
priorities = IssuePriority.active.to_a
assignees = people + [admin, nil]
issues = Array.new(60) do |i|
project = projects[i % projects.size]
version = project == infra ? vi : [v1, v2, v2, v3, nil][i % 5]
status = version == v1 || i % 9 == 7 ? closed_status : open_statuses[i % open_statuses.size]
start = today + rand(-40..20)
suffix = i >= titles.size ? " (#{i / titles.size + 1})" : ''
issue = Issue.new(project: project, tracker: project.trackers.to_a[i % project.trackers.size],
author: people[i % people.size], subject: titles[i % titles.size] + suffix,
description: "Steps to reproduce:\n\n1. Open the page\n2. Click **Save**\n3. See the error\n\n```ruby\nputs 'hello'\n```",
priority: priorities[i % priorities.size], assigned_to: assignees[i % assignees.size],
category: project.issue_categories.to_a[i % 3], fixed_version: version,
start_date: start, due_date: start + rand(3..30), estimated_hours: [2, 4, 8, 16, nil][i % 5],
done_ratio: status.is_closed? ? 100 : [0, 10, 30, 50, 80][i % 5], is_private: i == 5)
issue.status = status
issue.custom_field_values = { customer_field.id => %w[Acme Globex Initech][i % 3], review_field.id => (today + i).to_s }
issue.save!
issue
end
v1.update!(status: 'closed') # closed versions reject new issues, so close it afterwards
# Subtasks and relations (same project: cross-project relations are off by default)
showcase = issues[12] # open apollo issue: gets subtasks, attachments and watchers
[issues[24], issues[28]].each { |child| child.reload.update!(parent_issue_id: showcase.id) }
IssueRelation.create!(issue_from: showcase, issue_to: issues[32], relation_type: 'relates')
IssueRelation.create!(issue_from: issues[9], issue_to: issues[13], relation_type: 'blocks')
IssueRelation.create!(issue_from: issues[14], issue_to: issues[18], relation_type: 'precedes')
# Journals: notes and attribute changes
issues.first(15).each_with_index do |issue, i|
issue.reload.init_journal(people[i % 3], "Looked into this. #{i.even? ? 'Needs a decision from **product**.' : 'Fix is in review.'}")
issue.done_ratio = [issue.done_ratio.to_i + 10, 100].min unless issue.closed?
issue.save!
end
showcase.watcher_users << people[1..2]
# Attachments
# 160x90 solid-color RGB PNG, built by hand so the seed needs no image file
png_chunk = ->(type, data) { [data.bytesize].pack('N') + type + data + [Zlib.crc32(type + data)].pack('N') }
png = "\x89PNG\r\n\x1a\n".b + png_chunk.('IHDR', [160, 90, 8, 2, 0, 0, 0].pack('NNC5')) +
png_chunk.('IDAT', Zlib::Deflate.deflate(([0] + [103, 80, 164] * 160).pack('C*') * 90)) + png_chunk.('IEND', '')
[['screenshot.png', png, 'image/png'], ['notes.txt', "Some notes\n", 'text/plain']].each do |name, data, type|
Attachment.create!(file: StringIO.new(data), author: admin, filename: name, content_type: type, container: showcase)
end
# Wiki
wiki = apollo.reload.wiki
start_text = <<~MD
# Apollo wiki
{{toc}}
## Overview
The customer portal lets clients see **tickets**, *invoices* and `usage`.
> Keep this page short. Details go into child pages.
## Team
| Name | Role | Since |
|------|------|-------|
| Jane Doe | Product owner | 2024 |
| Max Smith | Backend | 2025 |
| Linh Nguyen | Frontend | 2026 |
## Setup
```ruby
class Portal
def initialize(user)
@user = user # current user
end
end
```
- [ ] Install dependencies
- [x] Configure database
See [[Architecture]] and issue #13.
MD
page = WikiPage.new(wiki: wiki, title: wiki.start_page)
page.content = WikiContent.new(page: page, text: start_text, author: admin)
page.save!
child = WikiPage.new(wiki: wiki, title: 'Architecture', parent: page)
child.content = WikiContent.new(page: child, text: "# Architecture\n\nThree tiers: web, API, database.", author: people[1])
child.save!
# News, forum, documents
news = News.create!(project: apollo, title: 'Version 2.0 beta available', summary: 'Try the new portal',
description: 'The beta is live on staging. Please report issues.', author: people[0])
Comment.create!(commented: news, author: people[1], content: 'Great work!')
board = Board.create!(project: apollo, name: 'General', description: 'General discussion')
topic = Message.create!(board: board, subject: 'Release planning', content: 'When do we freeze 2.0?', author: people[0])
Message.create!(board: board, parent: topic, subject: 'RE: Release planning', content: 'Next Friday.', author: people[1])
Document.create!(project: apollo, title: 'Requirements', description: 'Signed-off requirements.',
category: DocumentCategory.first)
# Time entries over the last 30 days
activities = TimeEntryActivity.active.to_a
issues.first(20).each_with_index do |issue, i|
user = people[i % 3]
TimeEntry.create!(project: issue.project, issue: issue, user: user, author: user, hours: [0.5, 1, 2, 3.5][i % 4],
spent_on: today - (i % 30), activity: activities[i % activities.size], comments: 'Work on issue')
end
# Saved public query: grouped, with totals
IssueQuery.create!(name: 'Open by assignee', user: admin, visibility: Query::VISIBILITY_PUBLIC,
group_by: 'assigned_to', totalable_names: %w[estimated_hours spent_hours],
column_names: %i[tracker status priority subject assigned_to due_date done_ratio])
end
puts "Seeded #{Project.count} projects, #{Issue.count} issues, #{User.count} users."
load File.join(__dir__, 'spread_activity.rb')
load File.join(__dir__, 'seed_extras.rb')
+67
View File
@@ -0,0 +1,67 @@
# Extra demo data for pages the base seed does not cover: repository, wiki diff, description diff.
# Loaded by seed.rb. Each block has its own guard, so rerunning the seed adds only what is missing.
require 'fileutils'
require 'open3'
require 'tmpdir'
apollo = Project.find_by!(identifier: 'apollo')
admin = User.find_by!(login: 'admin')
User.current = admin
# Git repository: built in a work tree, then cloned bare into the files volume (persists restarts).
unless apollo.repository
bare = '/usr/src/redmine/files/demo-repo.git'
work = Dir.mktmpdir
git = lambda do |*args|
out, status = Open3.capture2e('git', '-C', work, *args)
raise "git #{args.join(' ')} failed: #{out}" unless status.success?
end
env = { 'user.name' => 'Jane Doe', 'user.email' => 'jdoe@example.com' }.flat_map { |k, v| ['-c', "#{k}=#{v}"] }
git.('init', '-q', '-b', 'main')
File.write(File.join(work, 'README.md'), "# Apollo\n\nCustomer portal.\n")
FileUtils.mkdir_p(File.join(work, 'lib'))
File.write(File.join(work, 'lib/portal.rb'), "class Portal\n def initialize(user)\n @user = user\n end\nend\n")
git.(*env, 'add', '.')
git.(*env, 'commit', '-q', '-m', 'Initial import')
File.write(File.join(work, 'lib/portal.rb'),
"class Portal\n def initialize(user, locale = :en)\n @user = user\n @locale = locale\n end\n\n def greeting\n \"Hello, \#{@user}\"\n end\nend\n")
git.(*env, 'commit', '-q', '-am', 'Add locale and greeting (refs #13)')
File.write(File.join(work, 'README.md'), "# Apollo\n\nCustomer portal.\n\n## Setup\n\nRun `bin/setup`.\n")
git.(*env, 'commit', '-q', '-am', 'Document setup')
FileUtils.rm_rf(bare)
out, status = Open3.capture2e('git', 'clone', '-q', '--bare', work, bare)
raise "git clone failed: #{out}" unless status.success?
FileUtils.rm_rf(work)
FileUtils.chown_R('redmine', 'redmine', bare) if Process.uid.zero? # the app runs as redmine
apollo.enabled_module_names = apollo.enabled_module_names | ['repository']
repo = Repository::Git.create!(project: apollo, url: bare, identifier: 'portal', is_default: true)
repo.fetch_changesets
end
# Second wiki version, so history and diff have something to compare
start = apollo.wiki.find_page(apollo.wiki.start_page)
if start && start.content.version == 1
start.content.text = start.content.text.sub('Keep this page short.', 'Keep this page short and current.') +
"\n## Release\n\nVersion 2.0 ships at the end of the sprint.\n"
start.content.author = User.find_by!(login: 'jdoe')
start.content.comments = 'Add release section'
start.content.save!
end
# Description change on the showcase issue, so the journal has a "diff" link
showcase = Issue.find(13) # the issue the screenshot checklist shows
unless showcase.journals.joins(:details).exists?(journal_details: { prop_key: 'description' })
showcase.init_journal(User.find_by!(login: 'msmith'))
showcase.description = "#{showcase.description}\n\nAlso happens in the API search endpoint."
showcase.save!
end
# Second forum, so the forum index lists boards instead of redirecting to the only one
Board.create!(project: apollo, name: 'Support', description: 'Questions from customers') if apollo.boards.count < 2
puts "Extras: #{Repository.count} repositories, wiki v#{start&.content&.reload&.version}, " \
"#{JournalDetail.where(prop_key: 'description').count} description diffs, #{apollo.boards.count} forums."
+60
View File
@@ -0,0 +1,60 @@
# Spreads demo record timestamps over the last two weeks (working hours), so the activity page,
# "updated" columns and history show different days and times. Deterministic (based on ids),
# safe to run again. Called by seed.rb; can also run alone:
# docker compose exec redmine bin/rails runner /seed/spread_activity.rb
now = Time.current
today = Date.current
DAYS = 14
# Day `days_ago` at a working-hour time derived from `n`
at = lambda do |days_ago, n|
(today - days_ago).in_time_zone.change(hour: 8 + (n * 7) % 10, min: (n * 13) % 60)
end
not_future = ->(t, n) { t < now ? t : now - (n % 90 + 5).minutes }
ActiveRecord::Base.transaction do
Issue.order(:id).each_with_index do |issue, i|
created = at.(DAYS - 1 - (i % DAYS), i)
last = created
issue.journals.order(:id).each do |journal|
t = not_future.(created + (1 + journal.id % 3).days + (journal.id * 17 % 300).minutes, journal.id)
journal.update_columns(created_on: t, updated_on: t)
last = [last, t].max
end
closed = issue.closed? ? [last, created + 1.day].max : nil
issue.update_columns(created_on: created, updated_on: last, closed_on: closed && not_future.(closed, issue.id))
end
TimeEntry.find_each do |entry|
t = not_future.(entry.spent_on.in_time_zone.change(hour: 17, min: entry.id * 7 % 60), entry.id)
entry.update_columns(created_on: t, updated_on: t)
end
Attachment.find_each { |a| a.update_columns(created_on: not_future.(a.container.try(:created_on).to_time + 2.hours, a.id)) if a.container }
WikiContentVersion.order(:id).each_with_index do |version, i|
t = at.(9 - i * 2, version.id)
version.update_columns(updated_on: t)
version.page.content.update_columns(updated_on: t) if version.version == version.page.content.version
end
WikiPage.find_each { |page| page.update_columns(created_on: page.content.versions.minimum(:updated_on) || page.created_on) }
News.find_each do |news|
news.update_columns(created_on: at.(6, news.id))
news.comments.each_with_index { |c, i| c.update_columns(created_on: at.(5 - i, c.id), updated_on: at.(5 - i, c.id)) }
end
Message.where(parent_id: nil).find_each do |topic|
topic.update_columns(created_on: at.(4, topic.id), updated_on: at.(4, topic.id))
topic.children.each_with_index do |reply, i|
t = not_future.(at.(3 - i, reply.id), reply.id)
reply.update_columns(created_on: t, updated_on: t)
topic.update_columns(updated_on: t)
end
end
Document.find_each { |d| d.update_columns(created_on: at.(8, d.id)) }
end
puts "Spread activity over the last #{DAYS} days."
+94
View File
@@ -0,0 +1,94 @@
// Reports visible elements that still carry core Redmine shapes the theme should have replaced:
// 1-3px corners (core uses 3px, M3 uses 4/8/12/16/28px or full), core's grey box shadow
// (rgba of --oc-gray-9, theme shadows never use it), square boxes outlined on all four sides
// (core's card style, e.g. the old .mypage-box), and dashed or dotted borders.
// Checks desktop and mobile, because core's responsive.css brings its own shapes. Exits 1 on any hit.
// Usage: node check-core-leaks.mjs [--verbose]
import { chromium } from 'playwright';
const BASE = process.env.REDMINE_URL ?? 'http://127.0.0.1:3070';
const pages = [
'/my/page', '/projects', '/projects?display_type=list', '/projects/apollo', '/projects/apollo/issues', '/issues/13',
'/projects/apollo/issues/new', '/issues/13/edit', '/issues/bulk_edit?ids[]=13&ids[]=17', '/projects/apollo/issues/gantt',
'/projects/apollo/issues/calendar', '/projects/apollo/roadmap', '/versions/2', '/projects/apollo/activity',
'/projects/apollo/wiki', '/projects/apollo/wiki/Wiki/edit', '/news/1', '/boards/1/topics/1', '/projects/apollo/documents',
'/projects/apollo/files', '/projects/apollo/time_entries', '/projects/apollo/time_entries/report?criteria[]=user&columns=month',
'/search?q=login', '/my/account', '/admin', '/settings', '/users', '/roles/permissions',
'/workflows/edit?role_id=3&tracker_id=1', '/workflows/permissions?role_id=3&tracker_id=1',
'/projects/apollo/repository', '/projects/apollo/repository/portal/revisions', '/projects/apollo/repository/portal/entry/lib/portal.rb',
'/projects/apollo/repository/portal/annotate/lib/portal.rb', '/projects/apollo/repository/portal/revisions/main/diff',
'/projects/apollo/wiki/Wiki/history', '/projects/apollo/wiki/Wiki/diff?version=2&version_from=1', '/projects/apollo/settings',
'/projects/apollo/settings/members', '/projects/apollo/settings/versions', '/projects/apollo/settings/repositories', '/trackers',
'/issue_statuses', '/custom_fields', '/custom_fields/1/edit', '/enumerations', '/admin/info', '/admin/plugins',
'/projects/apollo/boards',
];
// Elements whose core shape is intended. Each entry needs a reason.
const ALLOW = [
'div.gantt_hdr', // Gantt chart header cells form the chart grid
];
const viewports = {
desktop: { viewport: { width: 1440, height: 900 } },
mobile: { viewport: { width: 390, height: 844 }, isMobile: true, hasTouch: true },
};
const browser = await chromium.launch();
const offenders = new Map(); // "viewport selector reason" -> pages
for (const [vpName, opts] of Object.entries(viewports)) {
const page = await browser.newPage(opts);
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'));
for (const path of pages) {
await page.goto(`${BASE}${path}`, { waitUntil: 'networkidle' });
const found = await page.evaluate((allow) => {
const describe = (el) => {
const parts = [];
for (let e = el; e && e !== document.body && parts.length < 3; e = e.parentElement) {
parts.unshift(e.id ? `#${e.id}` : e.tagName.toLowerCase() + [...e.classList].slice(0, 2).map((c) => `.${c}`).join(''));
if (e.id) break;
}
return parts.join(' > ');
};
const opaque = (color) => !/^rgba\(.*,\s*0\)$/.test(color) && color !== 'transparent';
const sides = ['Top', 'Right', 'Bottom', 'Left'];
const out = [];
for (const el of document.querySelectorAll('body *:not(script):not(style):not(svg *)')) {
const r = el.getBoundingClientRect();
if (!r.width || !r.height || allow.some((s) => el.matches(s))) continue;
const cs = getComputedStyle(el);
if (cs.visibility === 'hidden' || cs.display === 'none') continue;
const borders = sides.filter((s) => parseFloat(cs[`border${s}Width`]) > 0 && cs[`border${s}Style`] !== 'none'
&& opaque(cs[`border${s}Color`]));
const radii = ['TopLeft', 'TopRight', 'BottomRight', 'BottomLeft'].map((c) => parseFloat(cs[`border${c}Radius`]));
if (radii.some((x) => x >= 1 && x <= 3) && (borders.length || opaque(cs.backgroundColor))) {
out.push([describe(el), `${Math.max(...radii)}px corner`]);
}
if (cs.boxShadow.includes('rgba(33, 37, 41')) out.push([describe(el), 'core shadow']);
if (borders.length === 4 && radii.every((x) => x === 0) && r.width >= 100 && r.height >= 32
&& !['TABLE', 'TD', 'TH', 'TR'].includes(el.tagName)) {
out.push([describe(el), 'square outlined box']);
}
const dashed = borders.find((s) => ['dashed', 'dotted'].includes(cs[`border${s}Style`]));
if (dashed) out.push([describe(el), `${cs[`border${dashed}Style`]} border`]);
}
return out;
}, ALLOW);
for (const [sel, reason] of found) {
const key = `${vpName.padEnd(7)} ${sel} (${reason})`;
if (!offenders.has(key)) offenders.set(key, new Set());
offenders.get(key).add(path);
}
}
await page.close();
}
await browser.close();
const rows = [...offenders].sort(([a], [b]) => a.localeCompare(b));
for (const [key, where] of rows) {
console.log(`${key} ${process.argv.includes('--verbose') ? [...where].join(', ') : `${where.size} page${where.size > 1 ? 's' : ''}`}`);
}
console.log(rows.length ? `\n${rows.length} core shapes left` : 'No core shapes left.');
process.exit(rows.length ? 1 : 0);
+67
View File
@@ -0,0 +1,67 @@
// Reports visible text whose computed font size is off the theme's type scale.
// Core Redmine uses relative sizes (0.8em, 90%, …) that compound when nested; the theme should
// leave only M3 scale sizes. Exits 1 if anything is off-scale.
// Usage: node check-font-sizes.mjs [--verbose]
import { chromium } from 'playwright';
const BASE = process.env.REDMINE_URL ?? 'http://127.0.0.1:3070';
// M3 type scale in px (label-small 11 … display-large 57)
const SCALE = [11, 12, 14, 16, 22, 24, 28, 32, 36, 45, 57];
const pages = [
'/my/page', '/projects', '/projects/apollo', '/projects/apollo/issues', '/issues/13', '/projects/apollo/issues/new',
'/projects/apollo/roadmap', '/versions/2', '/projects/apollo/activity', '/projects/apollo/wiki', '/news/1',
'/boards/1/topics/1', '/projects/apollo/documents', '/projects/apollo/files', '/projects/apollo/time_entries',
'/projects/apollo/time_entries/report?criteria[]=user&columns=month', '/search?q=login', '/my/account',
'/admin', '/settings', '/users', '/roles/permissions', '/projects/apollo/issues/calendar',
'/projects/apollo/repository', '/projects/apollo/repository/portal/revisions', '/projects/apollo/repository/portal/entry/lib/portal.rb',
'/projects/apollo/repository/portal/annotate/lib/portal.rb', '/projects/apollo/repository/portal/revisions/main/diff',
'/projects/apollo/wiki/Wiki/history', '/projects/apollo/wiki/Wiki/diff?version=2&version_from=1', '/projects/apollo/settings',
'/projects/apollo/settings/members', '/projects/apollo/settings/versions', '/projects/apollo/settings/repositories', '/trackers',
'/issue_statuses', '/custom_fields', '/custom_fields/1/edit', '/enumerations', '/admin/info', '/admin/plugins',
'/workflows/permissions?role_id=3&tracker_id=1', '/projects/apollo/boards',
];
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'));
const offenders = new Map(); // "selector @ size" -> pages
for (const path of pages) {
await page.goto(`${BASE}${path}`, { waitUntil: 'networkidle' });
const found = await page.evaluate((scale) => {
const out = [];
const describe = (el) => {
const parts = [];
for (let e = el; e && e !== document.body && parts.length < 3; e = e.parentElement) {
parts.unshift(e.id ? `#${e.id}` : e.tagName.toLowerCase() + [...e.classList].slice(0, 2).map((c) => `.${c}`).join(''));
if (e.id) break;
}
return parts.join(' > ');
};
for (const el of document.querySelectorAll('body *:not(script):not(style):not(svg *)')) {
if (el.closest('table.gantt-table, .avatar')) continue; // Gantt is zoomed; avatar initials are graphics
const hasText = [...el.childNodes].some((n) => n.nodeType === 3 && n.textContent.trim());
if (!hasText || !el.offsetParent) continue;
const size = parseFloat(getComputedStyle(el).fontSize);
if (!scale.some((s) => Math.abs(s - size) < 0.3)) out.push([describe(el), Math.round(size * 10) / 10]);
}
return out;
}, SCALE);
for (const [sel, size] of found) {
const key = `${sel} @ ${size}px`;
if (!offenders.has(key)) offenders.set(key, new Set());
offenders.get(key).add(path);
}
}
await browser.close();
const rows = [...offenders].sort((a, b) => parseFloat(a[0].split('@ ')[1]) - parseFloat(b[0].split('@ ')[1]));
for (const [key, where] of rows) {
console.log(`${key} (${where.size} page${where.size > 1 ? 's' : ''}${process.argv.includes('--verbose') ? ': ' + [...where].join(', ') : ''})`);
}
console.log(rows.length ? `\n${rows.length} off-scale text styles` : 'All text on the type scale.');
process.exit(rows.length ? 1 : 0);
+48
View File
@@ -0,0 +1,48 @@
// Checks that each form label (.tabular forms, query filters) lines up with the field next to it.
// Usage: node check-form-alignment.mjs (exits 1 if any row is off by more than 2px)
import { chromium } from 'playwright';
const BASE = process.env.REDMINE_URL ?? 'http://127.0.0.1:3070';
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/apollo/time_entries/new', '/settings', '/my/account', '/users/5/edit',
'/projects/apollo/settings', '/custom_fields/1/edit', '/projects/apollo/wiki/Wiki/edit',
];
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;
for (const path of pages) {
await page.goto(`${BASE}${path}`, { waitUntil: 'networkidle' });
const rows = await page.evaluate(() => [
// Query filters: label text vs. operator select
...[...document.querySelectorAll('#filters-table .filter')].flatMap((row) => {
const label = row.querySelector('.field label'), control = row.querySelector('.operator select');
if (!label || !control || !control.offsetParent) return [];
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)) }];
}),
...[...document.querySelectorAll('.tabular p')].flatMap((row) => {
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');
if (!label || !control || !label.offsetParent || !control.offsetParent) return [];
const style = getComputedStyle(label);
const labelBox = label.getBoundingClientRect();
const controlBox = control.getBoundingClientRect();
const labelMiddle = labelBox.top + parseFloat(style.paddingTop) + parseFloat(style.lineHeight) / 2;
return [{ label: label.textContent.trim().slice(0, 30), offset: Math.round(labelMiddle - (controlBox.top + controlBox.height / 2)) }];
}),
]);
const bad = rows.filter((r) => Math.abs(r.offset) > 2);
failures += bad.length;
console.log(`${bad.length ? 'FAIL' : 'ok '} ${path} (${rows.length} rows)${bad.map((r) => `\n ${r.label}: ${r.offset}px`).join('')}`);
}
await browser.close();
process.exit(failures ? 1 : 0);
+3
View File
@@ -0,0 +1,3 @@
Subject,Tracker,Status,Priority,Assignee,Due date
Import check,Bug,New,Normal,,2026-12-01
Second row,Feature,New,High,,
1 Subject Tracker Status Priority Assignee Due date
2 Import check Bug New Normal 2026-12-01
3 Second row Feature New High
+40
View File
@@ -0,0 +1,40 @@
{
"name": "tonalmine-e2e",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "tonalmine-e2e",
"dependencies": {
"playwright": "^1"
}
},
"node_modules/playwright": {
"version": "1.63.0",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.63.0.tgz",
"integrity": "sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==",
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.63.0"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=20"
}
},
"node_modules/playwright-core": {
"version": "1.63.0",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.63.0.tgz",
"integrity": "sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==",
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=20"
}
}
}
}
+11
View File
@@ -0,0 +1,11 @@
{
"name": "tonalmine-e2e",
"private": true,
"type": "module",
"scripts": {
"screenshots": "node screenshots.mjs"
},
"dependencies": {
"playwright": "^1"
}
}
+143
View File
@@ -0,0 +1,143 @@
// Captures the page checklist in light/dark × desktop/mobile into ../screenshots/.
// Usage: node screenshots.mjs [filter] e.g. `node screenshots.mjs issue` for pages whose name contains "issue".
// Needs the dev server with seed data (see README).
import { chromium } from 'playwright';
import { mkdir } from 'node:fs/promises';
const BASE = process.env.REDMINE_URL ?? 'http://127.0.0.1:3070';
const OUT = new URL('../screenshots/', import.meta.url).pathname;
const filter = process.argv[2] ?? '';
const pages = [
['my-page', '/my/page'],
['projects-board', '/projects?display_type=board'],
['projects-list', '/projects?display_type=list'],
['project-overview', '/projects/apollo'],
['issues-grouped', '/projects/apollo/issues?set_filter=1&f[]=status_id&op[status_id]=*&group_by=status&t[]=estimated_hours&t[]=spent_hours'],
['issue-show', '/issues/13'],
['issue-new', '/projects/apollo/issues/new'],
['issue-edit', '/issues/13/edit'],
['issue-bulk-edit', '/issues/bulk_edit?ids[]=13&ids[]=17'],
['gantt', '/projects/apollo/issues/gantt'],
['calendar', '/projects/apollo/issues/calendar'],
['roadmap', '/projects/apollo/roadmap'],
['version', '/versions/2'],
['activity', '/projects/apollo/activity'],
['wiki', '/projects/apollo/wiki'],
['wiki-edit', '/projects/apollo/wiki/Wiki/edit'],
['news', '/news/1'],
['forum-topic', '/boards/1/topics/1'],
['documents', '/projects/apollo/documents'],
['files', '/projects/apollo/files'],
['time-entries', '/projects/apollo/time_entries'],
['time-report', '/projects/apollo/time_entries/report?criteria[]=user&columns=month'],
['search', '/search?q=login'],
['my-account', '/my/account'],
['admin', '/admin'],
['settings', '/settings'],
['users', '/users'],
['permissions', '/roles/permissions'],
['workflow', '/workflows/edit?role_id=3&tracker_id=1'],
['not-found', '/issues/99999'],
['repository', '/projects/apollo/repository'],
['repository-revisions', '/projects/apollo/repository/portal/revisions'],
['repository-entry', '/projects/apollo/repository/portal/entry/lib/portal.rb'],
['repository-annotate', '/projects/apollo/repository/portal/annotate/lib/portal.rb'],
['repository-diff', '/projects/apollo/repository/portal/revisions/main/diff'],
['wiki-history', '/projects/apollo/wiki/Wiki/history'],
['wiki-diff', '/projects/apollo/wiki/Wiki/diff?version=2&version_from=1'],
['project-settings', '/projects/apollo/settings'],
['project-settings-members', '/projects/apollo/settings/members'],
['project-settings-versions', '/projects/apollo/settings/versions'],
['project-settings-repositories', '/projects/apollo/settings/repositories'],
['trackers', '/trackers'],
['issue-statuses', '/issue_statuses'],
['custom-fields', '/custom_fields'],
['custom-field-edit', '/custom_fields/1/edit'],
['enumerations', '/enumerations'],
['admin-info', '/admin/info'],
['admin-plugins', '/admin/plugins'],
['workflow-fields', '/workflows/permissions?role_id=3&tracker_id=1'],
['forums', '/projects/apollo/boards'],
];
// Pages only reachable while logged out.
const publicPages = [
['login', '/login'],
['register', '/account/register'],
['lost-password', '/account/lost_password'],
];
// Pages that need an interaction before the capture.
const interactions = [
['issue-new-error', '/projects/apollo/issues/new', async (p) => {
await p.click('#issue-form input[name=commit]');
await p.waitForLoadState('networkidle');
}],
['issues-context-menu', '/projects/apollo/issues', async (p) => {
await p.click('table.list.issues tr.issue td.status', { button: 'right' });
await p.waitForSelector('#context-menu ul');
}, { desktopOnly: true }],
['journal-diff', '/issues/13', async (p) => {
// The journal ID differs per database, so follow the first description diff link.
await Promise.all([p.waitForURL(/\/journals\/\d+\/diff/), p.click('#history a[href*="/diff"]')]);
await p.waitForLoadState('networkidle');
}],
['import-mapping', '/issues/imports/new?project_id=apollo', async (p) => {
await p.setInputFiles('#file', new URL('fixtures/issues.csv', import.meta.url).pathname);
await Promise.all([p.waitForURL(/\/imports\/[^/]+\/settings/), p.click('input[type=submit]')]);
await Promise.all([p.waitForURL(/\/imports\/[^/]+\/mapping/), p.click('input[type=submit]')]);
await p.waitForLoadState('networkidle');
}],
['project-jump', '/projects/apollo-api/issues', async (p) => {
await p.click('#project-jump .drdn-trigger');
await p.waitForSelector('#project-jump .drdn-items');
}, { desktopOnly: true }],
['mobile-flyout', '/projects/apollo/issues', async (p) => {
await p.click('.mobile-toggle-button');
await p.waitForTimeout(300);
}, { mobileOnly: true, viewportOnly: true }],
];
const viewports = {
desktop: { width: 1440, height: 900 },
mobile: { width: 390, height: 844, isMobile: true, hasTouch: true },
};
await mkdir(OUT, { recursive: true });
const browser = await chromium.launch();
for (const scheme of ['light', 'dark']) {
for (const [vpName, vp] of Object.entries(viewports)) {
const { width, height, ...rest } = vp;
const context = await browser.newContext({ viewport: { width, height }, colorScheme: scheme, ...rest });
const page = await context.newPage();
const shot = (name, fullPage = true) =>
page.screenshot({ path: `${OUT}${name}-${scheme}-${vpName}.png`, fullPage });
for (const [name, path] of publicPages.filter(([n]) => n.includes(filter))) {
await page.goto(`${BASE}${path}`, { waitUntil: 'networkidle' });
await shot(name);
}
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'));
for (const [name, path] of pages.filter(([n]) => n.includes(filter))) {
await page.goto(`${BASE}${path}`, { waitUntil: 'networkidle' });
await shot(name);
}
for (const [name, path, act, opts = {}] of interactions.filter(([n]) => n.includes(filter))) {
if ((opts.desktopOnly && vpName !== 'desktop') || (opts.mobileOnly && vpName !== 'mobile')) continue;
await page.goto(`${BASE}${path}`, { waitUntil: 'networkidle' });
await act(page);
await shot(name, !opts.viewportOnly);
}
await context.close();
}
}
await browser.close();
console.log(`Screenshots written to ${OUT}`);
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

+93
View File
@@ -0,0 +1,93 @@
Copyright 2011 The Roboto Project Authors (https://github.com/googlefonts/roboto-classic) Roboto-Italic[wdth,wght].ttf: Copyright 2011 The Roboto Project Authors (https://github.com/googlefonts/roboto-classic)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,204 @@
This license applies to images/icons.svg (Material Symbols by Google, https://github.com/google/material-design-icons).
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" width="24" height="24"><path fill="#cac4cf" d="M480-360 280-560h400L480-360Z"/></svg>

After

Width:  |  Height:  |  Size: 151 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" width="24" height="24"><path fill="#49454e" d="M480-360 280-560h400L480-360Z"/></svg>

After

Width:  |  Height:  |  Size: 151 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" width="16" height="16"><path fill="#cac4cf" d="M560-240 320-480l240-240 56 56-184 184 184 184-56 56Z"/></svg>

After

Width:  |  Height:  |  Size: 175 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" width="16" height="16"><path fill="#49454e" d="M560-240 320-480l240-240 56 56-184 184 184 184-56 56Z"/></svg>

After

Width:  |  Height:  |  Size: 175 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" width="16" height="16"><path fill="#cac4cf" d="M504-480 320-664l56-56 240 240-240 240-56-56 184-184Z"/></svg>

After

Width:  |  Height:  |  Size: 175 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" width="16" height="16"><path fill="#49454e" d="M504-480 320-664l56-56 240 240-240 240-56-56 184-184Z"/></svg>

After

Width:  |  Height:  |  Size: 175 B

+123
View File
@@ -0,0 +1,123 @@
<svg xmlns="http://www.w3.org/2000/svg">
<!-- Generated by tools/build_icons.rb from tools/icon_map.yml. Do not edit by hand.
Icons: Material Symbols Outlined by Google (github.com/google/material-design-icons),
Apache License 2.0, see LICENSE-material-symbols.txt. -->
<symbol id="icon--3-bullets" viewBox="0 -960 960 960"><path d="M240-400q-33 0-56.5-23.5T160-480q0-33 23.5-56.5T240-560q33 0 56.5 23.5T320-480q0 33-23.5 56.5T240-400Zm240 0q-33 0-56.5-23.5T400-480q0-33 23.5-56.5T480-560q33 0 56.5 23.5T560-480q0 33-23.5 56.5T480-400Zm240 0q-33 0-56.5-23.5T640-480q0-33 23.5-56.5T720-560q33 0 56.5 23.5T800-480q0 33-23.5 56.5T720-400Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--add" viewBox="0 -960 960 960"><path d="M440-280h80v-160h160v-80H520v-160h-80v160H280v80h160v160Zm40 200q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--alert-circle" viewBox="0 -960 960 960"><path d="M480-280q17 0 28.5-11.5T520-320q0-17-11.5-28.5T480-360q-17 0-28.5 11.5T440-320q0 17 11.5 28.5T480-280Zm-40-160h80v-240h-80v240Zm40 360q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--angle-down" viewBox="0 -960 960 960"><path d="M480-345 240-585l56-56 184 184 184-184 56 56-240 240Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--angle-left" viewBox="0 -960 960 960"><path d="M560-240 320-480l240-240 56 56-184 184 184 184-56 56Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--angle-right" viewBox="0 -960 960 960"><path d="M504-480 320-664l56-56 240 240-240 240-56-56 184-184Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--angle-up" viewBox="0 -960 960 960"><path d="m296-345-56-56 240-240 240 240-56 56-184-184-184 184Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--application-gzip" viewBox="0 -960 960 960"><path d="M640-480v-80h80v80h-80Zm0 80h-80v-80h80v80Zm0 80v-80h80v80h-80ZM447-640l-80-80H160v480h400v-80h80v80h160v-400H640v80h-80v-80H447ZM160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h240l80 80h320q33 0 56.5 23.5T880-640v400q0 33-23.5 56.5T800-160H160Zm0-80v-480 480Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--application-javascript" viewBox="0 -960 960 960"><path d="M300-360q-25 0-42.5-17.5T240-420v-40h60v40h60v-180h60v180q0 25-17.5 42.5T360-360h-60Zm220 0q-17 0-28.5-11.5T480-400v-40h60v20h80v-40H520q-17 0-28.5-11.5T480-500v-60q0-17 11.5-28.5T520-600h120q17 0 28.5 11.5T680-560v40h-60v-20h-80v40h100q17 0 28.5 11.5T680-460v60q0 17-11.5 28.5T640-360H520Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--application-pdf" viewBox="0 -960 960 960"><path d="M360-460h40v-80h40q17 0 28.5-11.5T480-580v-40q0-17-11.5-28.5T440-660h-80v200Zm40-120v-40h40v40h-40Zm120 120h80q17 0 28.5-11.5T640-500v-120q0-17-11.5-28.5T600-660h-80v200Zm40-40v-120h40v120h-40Zm120 40h40v-80h40v-40h-40v-40h40v-40h-80v200ZM320-240q-33 0-56.5-23.5T240-320v-480q0-33 23.5-56.5T320-880h480q33 0 56.5 23.5T880-800v480q0 33-23.5 56.5T800-240H320Zm0-80h480v-480H320v480ZM160-80q-33 0-56.5-23.5T80-160v-560h80v560h560v80H160Zm160-720v480-480Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--application-zip" viewBox="0 -960 960 960"><path d="M640-480v-80h80v80h-80Zm0 80h-80v-80h80v80Zm0 80v-80h80v80h-80ZM447-640l-80-80H160v480h400v-80h80v80h160v-400H640v80h-80v-80H447ZM160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h240l80 80h320q33 0 56.5 23.5T880-640v400q0 33-23.5 56.5T800-160H160Zm0-80v-480 480Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--apps" viewBox="0 -960 960 960"><path d="M240-160q-33 0-56.5-23.5T160-240q0-33 23.5-56.5T240-320q33 0 56.5 23.5T320-240q0 33-23.5 56.5T240-160Zm240 0q-33 0-56.5-23.5T400-240q0-33 23.5-56.5T480-320q33 0 56.5 23.5T560-240q0 33-23.5 56.5T480-160Zm240 0q-33 0-56.5-23.5T640-240q0-33 23.5-56.5T720-320q33 0 56.5 23.5T800-240q0 33-23.5 56.5T720-160ZM240-400q-33 0-56.5-23.5T160-480q0-33 23.5-56.5T240-560q33 0 56.5 23.5T320-480q0 33-23.5 56.5T240-400Zm240 0q-33 0-56.5-23.5T400-480q0-33 23.5-56.5T480-560q33 0 56.5 23.5T560-480q0 33-23.5 56.5T480-400Zm240 0q-33 0-56.5-23.5T640-480q0-33 23.5-56.5T720-560q33 0 56.5 23.5T800-480q0 33-23.5 56.5T720-400ZM240-640q-33 0-56.5-23.5T160-720q0-33 23.5-56.5T240-800q33 0 56.5 23.5T320-720q0 33-23.5 56.5T240-640Zm240 0q-33 0-56.5-23.5T400-720q0-33 23.5-56.5T480-800q33 0 56.5 23.5T560-720q0 33-23.5 56.5T480-640Zm240 0q-33 0-56.5-23.5T640-720q0-33 23.5-56.5T720-800q33 0 56.5 23.5T800-720q0 33-23.5 56.5T720-640Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--arrow-narrow-left" viewBox="0 -960 960 960"><path d="M400-240 160-480l240-240 56 58-142 142h486v80H314l142 142-56 58Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--arrow-narrow-right" viewBox="0 -960 960 960"><path d="m560-240-56-58 142-142H160v-80h486L504-662l56-58 240 240-240 240Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--arrow-right" viewBox="0 -960 960 960"><path d="M647-440H160v-80h487L423-744l57-56 320 320-320 320-57-56 224-224Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--attachment" viewBox="0 -960 960 960"><path d="M720-330q0 104-73 177T470-80q-104 0-177-73t-73-177v-370q0-75 52.5-127.5T400-880q75 0 127.5 52.5T580-700v350q0 46-32 78t-78 32q-46 0-78-32t-32-78v-370h80v370q0 13 8.5 21.5T470-320q13 0 21.5-8.5T500-350v-350q-1-42-29.5-71T400-800q-42 0-71 29t-29 71v370q-1 71 49 120.5T470-160q70 0 119-49.5T640-330v-390h80v390Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--bookmark-add" viewBox="0 -960 960 960"><path d="M200-120v-640q0-33 23.5-56.5T280-840h240v80H280v518l200-86 200 86v-278h80v400L480-240 200-120Zm80-640h240-240Zm400 160v-80h-80v-80h80v-80h80v80h80v80h-80v80h-80Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--bookmark-delete" viewBox="0 -960 960 960"><path d="M840-680H600v-80h240v80ZM200-120v-640q0-33 23.5-56.5T280-840h240v80H280v518l200-86 200 86v-278h80v400L480-240 200-120Zm80-640h240-240Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--bookmarked" viewBox="0 -960 960 960"><path d="M200-120v-640q0-33 23.5-56.5T280-840h400q33 0 56.5 23.5T760-760v640L480-240 200-120Zm80-122 200-86 200 86v-518H280v518Zm0-518h400-400Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--bulb" viewBox="0 -960 960 960"><path d="M480-80q-33 0-56.5-23.5T400-160h160q0 33-23.5 56.5T480-80ZM320-200v-80h320v80H320Zm10-120q-69-41-109.5-110T180-580q0-125 87.5-212.5T480-880q125 0 212.5 87.5T780-580q0 81-40.5 150T630-320H330Zm24-80h252q45-32 69.5-79T700-580q0-92-64-156t-156-64q-92 0-156 64t-64 156q0 54 24.5 101t69.5 79Zm126 0Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--bullet-end" viewBox="0 -960 960 960"><path d="m480-320 56-56-64-64h168v-80H472l64-64-56-56-160 160 160 160Zm0 240q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--bullet-go" viewBox="0 -960 960 960"><path d="m480-320 160-160-160-160-56 56 64 64H320v80h168l-64 64 56 56Zm0 240q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--bullet-go-end" viewBox="0 -960 960 960"><path d="M480-120 80-600l120-240h560l120 240-400 480Zm-95-520h190l-60-120h-70l-60 120Zm55 347v-267H218l222 267Zm80 0 222-267H520v267Zm144-347h106l-60-120H604l60 120Zm-474 0h106l60-120H250l-60 120Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--cancel" viewBox="0 -960 960 960"><path d="M280-200v-80h284q63 0 109.5-40T720-420q0-60-46.5-100T564-560H312l104 104-56 56-200-200 200-200 56 56-104 104h252q97 0 166.5 63T800-420q0 94-69.5 157T564-200H280Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--changeset" viewBox="0 -960 960 960"><path d="M320-240 80-480l240-240 57 57-184 184 183 183-56 56Zm320 0-57-57 184-184-183-183 56-56 240 240-240 240Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--checked" viewBox="0 -960 960 960"><path d="M382-240 154-468l57-57 171 171 367-367 57 57-424 424Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--chevrons-left" viewBox="0 -960 960 960"><path d="M440-240 200-480l240-240 56 56-183 184 183 184-56 56Zm264 0L464-480l240-240 56 56-183 184 183 184-56 56Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--chevrons-right" viewBox="0 -960 960 960"><path d="M383-480 200-664l56-56 240 240-240 240-56-56 183-184Zm264 0L464-664l56-56 240 240-240 240-56-56 183-184Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--circle-dot-filled" viewBox="0 -960 960 960"><path d="M480-280q83 0 141.5-58.5T680-480q0-83-58.5-141.5T480-680q-83 0-141.5 58.5T280-480q0 83 58.5 141.5T480-280Zm0 200q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--circle-minus" viewBox="0 -960 960 960"><path d="M280-440h400v-80H280v80ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--clear-query" viewBox="0 -960 960 960"><path d="m592-481-57-57 143-182H353l-80-80h487q25 0 36 22t-4 42L592-481ZM791-56 560-287v87q0 17-11.5 28.5T520-160h-80q-17 0-28.5-11.5T400-200v-247L56-791l56-57 736 736-57 56ZM535-538Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--close" viewBox="0 -960 960 960"><path d="m256-200-56-56 224-224-224-224 56-56 224 224 224-224 56 56-224 224 224 224-56 56-224-224-224 224Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--comment" viewBox="0 -960 960 960"><path d="M240-400h480v-80H240v80Zm0-120h480v-80H240v80Zm0-120h480v-80H240v80ZM880-80 720-240H160q-33 0-56.5-23.5T80-320v-480q0-33 23.5-56.5T160-880h640q33 0 56.5 23.5T880-800v720ZM160-320h594l46 45v-525H160v480Zm0 0v-480 480Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--comments" viewBox="0 -960 960 960"><path d="M280-240q-17 0-28.5-11.5T240-280v-80h520v-360h80q17 0 28.5 11.5T880-680v600L720-240H280ZM80-280v-560q0-17 11.5-28.5T120-880h520q17 0 28.5 11.5T680-840v360q0 17-11.5 28.5T640-440H240L80-280Zm520-240v-280H160v280h440Zm-440 0v-280 280Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--copy" viewBox="0 -960 960 960"><path d="M360-240q-33 0-56.5-23.5T280-320v-480q0-33 23.5-56.5T360-880h360q33 0 56.5 23.5T800-800v480q0 33-23.5 56.5T720-240H360Zm0-80h360v-480H360v480ZM200-80q-33 0-56.5-23.5T120-160v-560h80v560h440v80H200Zm160-240v-480 480Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--copy-link" viewBox="0 -960 960 960"><path d="M440-280H280q-83 0-141.5-58.5T80-480q0-83 58.5-141.5T280-680h160v80H280q-50 0-85 35t-35 85q0 50 35 85t85 35h160v80ZM320-440v-80h320v80H320Zm200 160v-80h160q50 0 85-35t35-85q0-50-35-85t-85-35H520v-80h160q83 0 141.5 58.5T880-480q0 83-58.5 141.5T680-280H520Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--copy-pre-content" viewBox="0 -960 960 960"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h167q11-35 43-57.5t70-22.5q40 0 71.5 22.5T594-840h166q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm0-80h560v-560h-80v120H280v-120h-80v560Zm280-560q17 0 28.5-11.5T520-800q0-17-11.5-28.5T480-840q-17 0-28.5 11.5T440-800q0 17 11.5 28.5T480-760Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--custom-fields" viewBox="0 -960 960 960"><path d="M160-520q-33 0-56.5-23.5T80-600v-120q0-33 23.5-56.5T160-800h360v280H160Zm0-80h280v-120H160v120Zm0 440q-33 0-56.5-23.5T80-240v-120q0-33 23.5-56.5T160-440h440v280H160Zm0-80h360v-120H160v120Zm520 80v-360h-80v-280h280l-80 200h80L680-160ZM190-270h60v-60h-60v60Zm0-360h60v-60h-60v60Zm-30 30v-120 120Zm0 360v-120 120Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--del" viewBox="0 -960 960 960"><path d="M280-120q-33 0-56.5-23.5T200-200v-520h-40v-80h200v-40h240v40h200v80h-40v520q0 33-23.5 56.5T680-120H280Zm400-600H280v520h400v-520ZM360-280h80v-360h-80v360Zm160 0h80v-360h-80v360ZM280-720v520-520Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--document" viewBox="0 -960 960 960"><path d="M320-240h320v-80H320v80Zm0-160h320v-80H320v80ZM240-80q-33 0-56.5-23.5T160-160v-640q0-33 23.5-56.5T240-880h320l240 240v480q0 33-23.5 56.5T720-80H240Zm280-520v-200H240v640h480v-440H520ZM240-800v200-200 640-640Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--download" viewBox="0 -960 960 960"><path d="M480-320 280-520l56-58 104 104v-326h80v326l104-104 56 58-200 200ZM240-160q-33 0-56.5-23.5T160-240v-120h80v120h480v-120h80v120q0 33-23.5 56.5T720-160H240Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--edit" viewBox="0 -960 960 960"><path d="M200-200h57l391-391-57-57-391 391v57Zm-80 80v-170l528-527q12-11 26.5-17t30.5-6q16 0 31 6t26 18l55 56q12 11 17.5 26t5.5 30q0 16-5.5 30.5T817-647L290-120H120Zm640-584-56-56 56 56Zm-141 85-28-29 57 57-29-28Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--email" viewBox="0 -960 960 960"><path d="M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h640q33 0 56.5 23.5T880-720v480q0 33-23.5 56.5T800-160H160Zm320-280L160-640v400h640v-400L480-440Zm0-80 320-200H160l320 200ZM160-640v-80 480-400Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--email-disabled" viewBox="0 -960 960 960"><path d="M383-463Zm194-34ZM791-55 686-160H160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800l80 80h-80v480h446L55-791l57-57 736 736-57 57Zm80-148-71-71v-366L575-499l-49-49 274-172H354l-80-80h526q33 0 56.5 23.5T880-720v480q0 10-2 19.5t-7 17.5Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--fav" viewBox="0 -960 960 960"><path d="m354-287 126-76 126 77-33-144 111-96-146-13-58-136-58 135-146 13 111 97-33 143ZM233-120l65-281L80-590l288-25 112-265 112 265 288 25-218 189 65 281-247-149-247 149Zm247-350Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--file" viewBox="0 -960 960 960"><path d="M240-80q-33 0-56.5-23.5T160-160v-640q0-33 23.5-56.5T240-880h320l240 240v480q0 33-23.5 56.5T720-80H240Zm280-520v-200H240v640h480v-440H520ZM240-800v200-200 640-640Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--file-music" viewBox="0 -960 960 960"><path d="M430-200q38 0 64-26t26-64v-150h120v-80H480v155q-11-8-23.5-11.5T430-380q-38 0-64 26t-26 64q0 38 26 64t64 26ZM240-80q-33 0-56.5-23.5T160-160v-640q0-33 23.5-56.5T240-880h320l240 240v480q0 33-23.5 56.5T720-80H240Zm280-520v-200H240v640h480v-440H520ZM240-800v200-200 640-640Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--file-type-docx" viewBox="0 -960 960 960"><path d="M320-440h320v-80H320v80Zm0 120h320v-80H320v80Zm0 120h200v-80H320v80ZM240-80q-33 0-56.5-23.5T160-160v-640q0-33 23.5-56.5T240-880h320l240 240v480q0 33-23.5 56.5T720-80H240Zm280-520v-200H240v640h480v-440H520ZM240-800v200-200 640-640Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--file-type-ppt" viewBox="0 -960 960 960"><path d="m380-300 280-180-280-180v360ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm0-80h560v-560H200v560Zm0-560v560-560Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--file-type-xls" viewBox="0 -960 960 960"><path d="M120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200q-33 0-56.5-23.5T120-200Zm80-400h560v-160H200v160Zm213 200h134v-120H413v120Zm0 200h134v-120H413v120ZM200-400h133v-120H200v120Zm427 0h133v-120H627v120ZM200-200h133v-120H200v120Zm427 0h133v-120H627v120Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--folder" viewBox="0 -960 960 960"><path d="M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h240l80 80h320q33 0 56.5 23.5T880-640v400q0 33-23.5 56.5T800-160H160Zm0-80h640v-400H447l-80-80H160v480Zm0 0v-480 480Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--folder-open" viewBox="0 -960 960 960"><path d="M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h240l80 80h320q33 0 56.5 23.5T880-640H447l-80-80H160v480l96-320h684L837-217q-8 26-29.5 41.5T760-160H160Zm84-80h516l72-240H316l-72 240Zm0 0 72-240-72 240Zm-84-400v-80 80Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--group" viewBox="0 -960 960 960"><path d="M40-160v-112q0-34 17.5-62.5T104-378q62-31 126-46.5T360-440q66 0 130 15.5T616-378q29 15 46.5 43.5T680-272v112H40Zm720 0v-120q0-44-24.5-84.5T666-434q51 6 96 20.5t84 35.5q36 20 55 44.5t19 53.5v120H760ZM360-480q-66 0-113-47t-47-113q0-66 47-113t113-47q66 0 113 47t47 113q0 66-47 113t-113 47Zm400-160q0 66-47 113t-113 47q-11 0-28-2.5t-28-5.5q27-32 41.5-71t14.5-81q0-42-14.5-81T544-792q14-5 28-6.5t28-1.5q66 0 113 47t47 113ZM120-240h480v-32q0-11-5.5-20T580-306q-54-27-109-40.5T360-360q-56 0-111 13.5T140-306q-9 5-14.5 14t-5.5 20v32Zm240-320q33 0 56.5-23.5T440-640q0-33-23.5-56.5T360-720q-33 0-56.5 23.5T280-640q0 33 23.5 56.5T360-560Zm0 320Zm0-400Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--help" viewBox="0 -960 960 960"><path d="M440-280h80v-240h-80v240Zm40-320q17 0 28.5-11.5T520-640q0-17-11.5-28.5T480-680q-17 0-28.5 11.5T440-640q0 17 11.5 28.5T480-600Zm0 520q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--history" viewBox="0 -960 960 960"><path d="M480-120q-138 0-240.5-91.5T122-440h82q14 104 92.5 172T480-200q117 0 198.5-81.5T760-480q0-117-81.5-198.5T480-760q-69 0-129 32t-101 88h110v80H120v-240h80v94q51-64 124.5-99T480-840q75 0 140.5 28.5t114 77q48.5 48.5 77 114T840-480q0 75-28.5 140.5t-77 114q-48.5 48.5-114 77T480-120Zm112-192L440-464v-216h80v184l128 128-56 56Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--hourglass" viewBox="0 -960 960 960"><path d="M320-160h320v-120q0-66-47-113t-113-47q-66 0-113 47t-47 113v120Zm160-360q66 0 113-47t47-113v-120H320v120q0 66 47 113t113 47ZM160-80v-80h80v-120q0-61 28.5-114.5T348-480q-51-32-79.5-85.5T240-680v-120h-80v-80h640v80h-80v120q0 61-28.5 114.5T612-480q51 32 79.5 85.5T720-280v120h80v80H160Zm320-80Zm0-640Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--import" viewBox="0 -960 960 960"><path d="M467-120q-73-1-136-14t-110-34.5q-47-21.5-74-50T120-280q0 33 27 61.5t74 50Q268-147 331-134t136 14Zm-15-200q-38-2-73.5-6.5t-67.5-12q-32-7.5-60-17.5t-51-23q23 13 51 23t60 17.5q32 7.5 67.5 12T452-320Zm28-279q89 0 179-26.5T760-679q-11-29-100.5-55T480-760q-91 0-178.5 25.5T200-679q14 27 101.5 53.5T480-599Zm220 479h40v-164l72 72 28-28-120-120-120 120 28 28 72-72v164Zm20 80q-83 0-141.5-58.5T520-240q0-83 58.5-141.5T720-440q83 0 141.5 58.5T920-240q0 83-58.5 141.5T720-40ZM443-201q3 22 9 42t15 39q-73-1-136-14t-110-34.5q-47-21.5-74-50T120-280v-400q0-66 105.5-113T480-840q149 0 254.5 47T840-680v187q-19-9-39-15t-41-9v-62q-52 29-124 44t-156 15q-85 0-157-15t-123-44v101q51 47 130.5 62.5T480-400h11q-13 18-22.5 38T452-320q-76-4-141-18.5T200-379v99q7 13 30 26.5t56 24q33 10.5 73.5 18T443-201Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--issue" viewBox="0 -960 960 960"><path d="M200-200h360v-200h200v-360H200v560Zm0 80q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v400L600-120H200Zm80-280v-80h200v80H280Zm0-160v-80h400v80H280Zm-80 360v-560 560Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--issue-closed" viewBox="0 -960 960 960"><path d="m424-312 282-282-56-56-226 226-114-114-56 56 170 170ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm0-80h560v-560H200v560Zm0-560v560-560Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--issue-edit" viewBox="0 -960 960 960"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h357l-80 80H200v560h560v-278l80-80v358q0 33-23.5 56.5T760-120H200Zm280-360ZM360-360v-170l367-367q12-12 27-18t30-6q16 0 30.5 6t26.5 18l56 57q11 12 17 26.5t6 29.5q0 15-5.5 29.5T897-728L530-360H360Zm481-424-56-56 56 56ZM440-440h56l232-232-28-28-29-28-231 231v57Zm260-260-29-28 29 28 28 28-28-28Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--issue-note" viewBox="0 -960 960 960"><path d="M440-400h80v-120h120v-80H520v-120h-80v120H320v80h120v120ZM80-80v-720q0-33 23.5-56.5T160-880h640q33 0 56.5 23.5T880-800v480q0 33-23.5 56.5T800-240H240L80-80Zm126-240h594v-480H160v525l46-45Zm-46 0v-480 480Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--key" viewBox="0 -960 960 960"><path d="M280-400q-33 0-56.5-23.5T200-480q0-33 23.5-56.5T280-560q33 0 56.5 23.5T360-480q0 33-23.5 56.5T280-400Zm0 160q-100 0-170-70T40-480q0-100 70-170t170-70q67 0 121.5 33t86.5 87h352l120 120-180 180-80-60-80 60-85-60h-47q-32 54-86.5 87T280-240Zm0-80q56 0 98.5-34t56.5-86h125l58 41 82-61 71 55 75-75-40-40H435q-14-52-56.5-86T280-640q-66 0-113 47t-47 113q0 66 47 113t113 47Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--link" viewBox="0 -960 960 960"><path d="M440-280H280q-83 0-141.5-58.5T80-480q0-83 58.5-141.5T280-680h160v80H280q-50 0-85 35t-35 85q0 50 35 85t85 35h160v80ZM320-440v-80h320v80H320Zm200 160v-80h160q50 0 85-35t35-85q0-50-35-85t-85-35H520v-80h160q83 0 141.5 58.5T880-480q0 83-58.5 141.5T680-280H520Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--link-add" viewBox="0 -960 960 960"><path d="M680-160v-120H560v-80h120v-120h80v120h120v80H760v120h-80ZM440-280H280q-83 0-141.5-58.5T80-480q0-83 58.5-141.5T280-680h160v80H280q-50 0-85 35t-35 85q0 50 35 85t85 35h160v80ZM320-440v-80h320v80H320Zm560-40h-80q0-50-35-85t-85-35H520v-80h160q83 0 141.5 58.5T880-480Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--link-break" viewBox="0 -960 960 960"><path d="m770-302-60-62q40-11 65-42.5t25-73.5q0-50-35-85t-85-35H520v-80h160q83 0 141.5 58.5T880-480q0 57-29.5 105T770-302ZM634-440l-80-80h86v80h-6ZM792-56 56-792l56-56 736 736-56 56ZM440-280H280q-83 0-141.5-58.5T80-480q0-69 42-123t108-71l74 74h-24q-50 0-85 35t-35 85q0 50 35 85t85 35h160v80ZM320-440v-80h65l79 80H320Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--list" viewBox="0 -960 960 960"><path d="M280-600v-80h560v80H280Zm0 160v-80h560v80H280Zm0 160v-80h560v80H280ZM160-600q-17 0-28.5-11.5T120-640q0-17 11.5-28.5T160-680q17 0 28.5 11.5T200-640q0 17-11.5 28.5T160-600Zm0 160q-17 0-28.5-11.5T120-480q0-17 11.5-28.5T160-520q17 0 28.5 11.5T200-480q0 17-11.5 28.5T160-440Zm0 160q-17 0-28.5-11.5T120-320q0-17 11.5-28.5T160-360q17 0 28.5 11.5T200-320q0 17-11.5 28.5T160-280Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--loader" viewBox="0 -960 960 960"><path d="M480-80q-82 0-155-31.5t-127.5-86Q143-252 111.5-325T80-480q0-83 31.5-155.5t86-127Q252-817 325-848.5T480-880q17 0 28.5 11.5T520-840q0 17-11.5 28.5T480-800q-133 0-226.5 93.5T160-480q0 133 93.5 226.5T480-160q133 0 226.5-93.5T800-480q0-17 11.5-28.5T840-520q17 0 28.5 11.5T880-480q0 82-31.5 155t-86 127.5q-54.5 54.5-127 86T480-80Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--lock" viewBox="0 -960 960 960"><path d="M240-80q-33 0-56.5-23.5T160-160v-400q0-33 23.5-56.5T240-640h40v-80q0-83 58.5-141.5T480-920q83 0 141.5 58.5T680-720v80h40q33 0 56.5 23.5T800-560v400q0 33-23.5 56.5T720-80H240Zm0-80h480v-400H240v400Zm240-120q33 0 56.5-23.5T560-360q0-33-23.5-56.5T480-440q-33 0-56.5 23.5T400-360q0 33 23.5 56.5T480-280ZM360-640h240v-80q0-50-35-85t-85-35q-50 0-85 35t-35 85v80ZM240-160v-400 400Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--maximize" viewBox="0 -960 960 960"><path d="M120-120v-200h80v120h120v80H120Zm520 0v-80h120v-120h80v200H640ZM120-640v-200h200v80H200v120h-80Zm640 0v-120H640v-80h200v200h-80Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--message" viewBox="0 -960 960 960"><path d="M80-80v-720q0-33 23.5-56.5T160-880h640q33 0 56.5 23.5T880-800v480q0 33-23.5 56.5T800-240H240L80-80Zm126-240h594v-480H160v525l46-45Zm-46 0v-480 480Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--message-report" viewBox="0 -960 960 960"><path d="M480-360q17 0 28.5-11.5T520-400q0-17-11.5-28.5T480-440q-17 0-28.5 11.5T440-400q0 17 11.5 28.5T480-360Zm-40-160h80v-240h-80v240ZM80-80v-720q0-33 23.5-56.5T160-880h640q33 0 56.5 23.5T880-800v480q0 33-23.5 56.5T800-240H240L80-80Zm126-240h594v-480H160v525l46-45Zm-46 0v-480 480Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--move" viewBox="0 -960 960 960"><path d="M806-440H320v-80h486l-62-62 56-58 160 160-160 160-56-58 62-62ZM600-600v-160H200v560h400v-160h80v160q0 33-23.5 56.5T600-120H200q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h400q33 0 56.5 23.5T680-760v160h-80Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--movie" viewBox="0 -960 960 960"><path d="m160-800 80 160h120l-80-160h80l80 160h120l-80-160h80l80 160h120l-80-160h120q33 0 56.5 23.5T880-720v480q0 33-23.5 56.5T800-160H160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800Zm0 240v320h640v-320H160Zm0 0v320-320Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--news" viewBox="0 -960 960 960"><path d="M160-120q-33 0-56.5-23.5T80-200v-640l67 67 66-67 67 67 67-67 66 67 67-67 67 67 66-67 67 67 67-67 66 67 67-67v640q0 33-23.5 56.5T800-120H160Zm0-80h280v-240H160v240Zm360 0h280v-80H520v80Zm0-160h280v-80H520v80ZM160-520h640v-120H160v120Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--package" viewBox="0 -960 960 960"><path d="M440-183v-274L200-596v274l240 139Zm80 0 240-139v-274L520-457v274Zm-80 92L160-252q-19-11-29.5-29T120-321v-318q0-22 10.5-40t29.5-29l280-161q19-11 40-11t40 11l280 161q19 11 29.5 29t10.5 40v318q0 22-10.5 40T800-252L520-91q-19 11-40 11t-40-11Zm200-528 77-44-237-137-78 45 238 136Zm-160 93 78-45-237-137-78 45 237 137Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--photo" viewBox="0 -960 960 960"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm0-80h560v-560H200v560Zm40-80h480L570-480 450-320l-90-120-120 160Zm-40 80v-560 560Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--plugins" viewBox="0 -960 960 960"><path d="M352-120H200q-33 0-56.5-23.5T120-200v-152q48 0 84-30.5t36-77.5q0-47-36-77.5T120-568v-152q0-33 23.5-56.5T200-800h160q0-42 29-71t71-29q42 0 71 29t29 71h160q33 0 56.5 23.5T800-720v160q42 0 71 29t29 71q0 42-29 71t-71 29v160q0 33-23.5 56.5T720-120H568q0-50-31.5-85T460-240q-45 0-76.5 35T352-120Zm-152-80h85q24-66 77-93t98-27q45 0 98 27t77 93h85v-240h80q8 0 14-6t6-14q0-8-6-14t-14-6h-80v-240H480v-80q0-8-6-14t-14-6q-8 0-14 6t-6 14v80H200v88q54 20 87 67t33 105q0 57-33 104t-87 68v88Zm260-260Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--project" viewBox="0 -960 960 960"><path d="M200-80q-33 0-56.5-23.5T120-160v-451q-18-11-29-28.5T80-680v-120q0-33 23.5-56.5T160-880h640q33 0 56.5 23.5T880-800v120q0 23-11 40.5T840-611v451q0 33-23.5 56.5T760-80H200Zm0-520v440h560v-440H200Zm-40-80h640v-120H160v120Zm200 280h240v-80H360v80Zm120 20Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--projects" viewBox="0 -960 960 960"><path d="M200-80q-33 0-56.5-23.5T120-160v-451q-18-11-29-28.5T80-680v-120q0-33 23.5-56.5T160-880h640q33 0 56.5 23.5T880-800v120q0 23-11 40.5T840-611v451q0 33-23.5 56.5T760-80H200Zm0-520v440h560v-440H200Zm-40-80h640v-120H160v120Zm200 280h240v-80H360v80Zm120 20Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--quote-filled" viewBox="0 -960 960 960"><path d="m228-240 92-160q-66 0-113-47t-47-113q0-66 47-113t113-47q66 0 113 47t47 113q0 23-5.5 42.5T458-480L320-240h-92Zm360 0 92-160q-66 0-113-47t-47-113q0-66 47-113t113-47q66 0 113 47t47 113q0 23-5.5 42.5T818-480L680-240h-92Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--reload" viewBox="0 -960 960 960"><path d="M480-160q-134 0-227-93t-93-227q0-134 93-227t227-93q69 0 132 28.5T720-690v-110h80v280H520v-80h168q-32-56-87.5-88T480-720q-100 0-170 70t-70 170q0 100 70 170t170 70q77 0 139-44t87-116h84q-28 106-114 173t-196 67Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--reorder" viewBox="0 -960 960 960"><path d="M120-200v-80h720v80H120Zm0-160v-80h720v80H120Zm0-160v-80h720v80H120Zm0-160v-80h720v80H120Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--reply" viewBox="0 -960 960 960"><path d="M760-200v-160q0-50-35-85t-85-35H273l144 144-57 56-240-240 240-240 57 56-144 144h367q83 0 141.5 58.5T840-360v160h-80Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--roles" viewBox="0 -960 960 960"><path d="M680-280q25 0 42.5-17.5T740-340q0-25-17.5-42.5T680-400q-25 0-42.5 17.5T620-340q0 25 17.5 42.5T680-280Zm0 120q31 0 57-14.5t42-38.5q-22-13-47-20t-52-7q-27 0-52 7t-47 20q16 24 42 38.5t57 14.5ZM480-80q-139-35-229.5-159.5T160-516v-244l320-120 320 120v227q-19-8-39-14.5t-41-9.5v-147l-240-90-240 90v188q0 47 12.5 94t35 89.5Q310-290 342-254t71 60q11 32 29 61t41 52q-1 0-1.5.5t-1.5.5Zm200 0q-83 0-141.5-58.5T480-280q0-83 58.5-141.5T680-480q83 0 141.5 58.5T880-280q0 83-58.5 141.5T680-80ZM480-494Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--save" viewBox="0 -960 960 960"><path d="M840-680v480q0 33-23.5 56.5T760-120H200q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h480l160 160Zm-80 34L646-760H200v560h560v-446ZM480-240q50 0 85-35t35-85q0-50-35-85t-85-35q-50 0-85 35t-35 85q0 50 35 85t85 35ZM240-560h360v-160H240v160Zm-40-86v446-560 114Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--search" viewBox="0 -960 960 960"><path d="M784-120 532-372q-30 24-69 38t-83 14q-109 0-184.5-75.5T120-580q0-109 75.5-184.5T380-840q109 0 184.5 75.5T640-580q0 44-14 83t-38 69l252 252-56 56ZM380-400q75 0 127.5-52.5T560-580q0-75-52.5-127.5T380-760q-75 0-127.5 52.5T200-580q0 75 52.5 127.5T380-400Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--server-authentication" viewBox="0 -960 960 960"><path d="M300-720q-25 0-42.5 17.5T240-660q0 25 17.5 42.5T300-600q25 0 42.5-17.5T360-660q0-25-17.5-42.5T300-720Zm0 400q-25 0-42.5 17.5T240-260q0 25 17.5 42.5T300-200q25 0 42.5-17.5T360-260q0-25-17.5-42.5T300-320ZM160-840h640q17 0 28.5 11.5T840-800v280q0 17-11.5 28.5T800-480H160q-17 0-28.5-11.5T120-520v-280q0-17 11.5-28.5T160-840Zm40 80v200h560v-200H200Zm-40 320h640q17 0 28.5 11.5T840-400v280q0 17-11.5 28.5T800-80H160q-17 0-28.5-11.5T120-120v-280q0-17 11.5-28.5T160-440Zm40 80v200h560v-200H200Zm0-400v200-200Zm0 400v200-200Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--settings" viewBox="0 -960 960 960"><path d="m370-80-16-128q-13-5-24.5-12T307-235l-119 50L78-375l103-78q-1-7-1-13.5v-27q0-6.5 1-13.5L78-585l110-190 119 50q11-8 23-15t24-12l16-128h220l16 128q13 5 24.5 12t22.5 15l119-50 110 190-103 78q1 7 1 13.5v27q0 6.5-2 13.5l103 78-110 190-118-50q-11 8-23 15t-24 12L590-80H370Zm70-80h79l14-106q31-8 57.5-23.5T639-327l99 41 39-68-86-65q5-14 7-29.5t2-31.5q0-16-2-31.5t-7-29.5l86-65-39-68-99 42q-22-23-48.5-38.5T533-694l-13-106h-79l-14 106q-31 8-57.5 23.5T321-633l-99-41-39 68 86 64q-5 15-7 30t-2 32q0 16 2 31t7 30l-86 65 39 68 99-42q22 23 48.5 38.5T427-266l13 106Zm42-180q58 0 99-41t41-99q0-58-41-99t-99-41q-59 0-99.5 41T342-480q0 58 40.5 99t99.5 41Zm-2-140Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--shield-check" viewBox="0 -960 960 960"><path d="m438-338 226-226-57-57-169 169-84-84-57 57 141 141Zm42 258q-139-35-229.5-159.5T160-516v-244l320-120 320 120v244q0 152-90.5 276.5T480-80Zm0-84q104-33 172-132t68-220v-189l-240-90-240 90v189q0 121 68 220t172 132Zm0-316Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--stats" viewBox="0 -960 960 960"><path d="M640-160v-280h160v280H640Zm-240 0v-640h160v640H400Zm-240 0v-440h160v440H160Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--summary" viewBox="0 -960 960 960"><path d="m422-232 207-248H469l29-227-185 267h139l-30 208ZM320-80l40-280H160l360-520h80l-40 320h240L400-80h-80Zm151-390Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--table-multiple" viewBox="0 -960 960 960"><path d="M480-160q-134 0-227-93t-93-227q0-134 93-227t227-93q69 0 132 28.5T720-690v-110h80v280H520v-80h168q-32-56-87.5-88T480-720q-100 0-170 70t-70 170q0 100 70 170t170 70q77 0 139-44t87-116h84q-28 106-114 173t-196 67Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--text-css" viewBox="0 -960 960 960"><path d="M420-360q-17 0-28.5-11.5T380-400v-40h60v20h80v-40H420q-17 0-28.5-11.5T380-500v-60q0-17 11.5-28.5T420-600h120q17 0 28.5 11.5T580-560v40h-60v-20h-80v40h100q17 0 28.5 11.5T580-460v60q0 17-11.5 28.5T540-360H420Zm260 0q-17 0-28.5-11.5T640-400v-40h60v20h80v-40H680q-17 0-28.5-11.5T640-500v-60q0-17 11.5-28.5T680-600h120q17 0 28.5 11.5T840-560v40h-60v-20h-80v40h100q17 0 28.5 11.5T840-460v60q0 17-11.5 28.5T800-360H680Zm-520 0q-17 0-28.5-11.5T120-400v-160q0-17 11.5-28.5T160-600h120q17 0 28.5 11.5T320-560v40h-60v-20h-80v120h80v-20h60v40q0 17-11.5 28.5T280-360H160Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--text-html" viewBox="0 -960 960 960"><path d="M0-360v-240h60v80h80v-80h60v240h-60v-100H60v100H0Zm310 0v-180h-70v-60h200v60h-70v180h-60Zm170 0v-200q0-17 11.5-28.5T520-600h180q17 0 28.5 11.5T740-560v200h-60v-180h-40v140h-60v-140h-40v180h-60Zm320 0v-240h60v180h100v60H800Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--text-plain" viewBox="0 -960 960 960"><path d="M320-240h320v-80H320v80Zm0-160h320v-80H320v80ZM240-80q-33 0-56.5-23.5T160-160v-640q0-33 23.5-56.5T240-880h320l240 240v480q0 33-23.5 56.5T720-80H240Zm280-520v-200H240v640h480v-440H520ZM240-800v200-200 640-640Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--text-x-c" viewBox="0 -960 960 960"><path d="m384-336 56-57-87-87 87-87-56-57-144 144 144 144Zm192 0 144-144-144-144-56 57 87 87-87 87 56 57ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm0-80h560v-560H200v560Zm0-560v560-560Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--text-x-csharp" viewBox="0 -960 960 960"><path d="m384-336 56-57-87-87 87-87-56-57-144 144 144 144Zm192 0 144-144-144-144-56 57 87 87-87 87 56 57ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm0-80h560v-560H200v560Zm0-560v560-560Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--text-x-java" viewBox="0 -960 960 960"><path d="m384-336 56-57-87-87 87-87-56-57-144 144 144 144Zm192 0 144-144-144-144-56 57 87 87-87 87 56 57ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm0-80h560v-560H200v560Zm0-560v560-560Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--text-x-php" viewBox="0 -960 960 960"><path d="M120-360v-240h140q24 0 42 18t18 42v40q0 24-18 42t-42 18h-80v80h-60Zm260 0v-240h60v80h80v-80h60v240h-60v-100h-80v100h-60Zm280 0v-240h140q24 0 42 18t18 42v40q0 24-18 42t-42 18h-80v80h-60ZM180-500h80v-40h-80v40Zm540 0h80v-40h-80v40Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--text-x-ruby" viewBox="0 -960 960 960"><path d="m384-336 56-57-87-87 87-87-56-57-144 144 144 144Zm192 0 144-144-144-144-56 57 87 87-87 87 56 57ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm0-80h560v-560H200v560Zm0-560v560-560Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--text-xml" viewBox="0 -960 960 960"><path d="M320-240 80-480l240-240 57 57-184 184 183 183-56 56Zm320 0-57-57 184-184-183-183 56-56 240 240-240 240Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--thumb-up" viewBox="0 -960 960 960"><path d="M720-120H280v-520l280-280 50 50q7 7 11.5 19t4.5 23v14l-44 174h258q32 0 56 24t24 56v80q0 7-2 15t-4 15L794-168q-9 20-30 34t-44 14Zm-360-80h360l120-280v-80H480l54-220-174 174v406Zm0-406v406-406Zm-80-34v80H160v360h120v80H80v-520h200Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--thumb-up-filled" viewBox="0 -960 960 960"><path d="M720-120H320v-520l280-280 50 50q7 7 11.5 19t4.5 23v14l-44 174h218q32 0 56 24t24 56v80q0 7-1.5 15t-4.5 15L794-168q-9 20-30 34t-44 14ZM240-640v520H80v-520h160Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--time" viewBox="0 -960 960 960"><path d="m612-292 56-56-148-148v-184h-80v216l172 172ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-400Zm0 320q133 0 226.5-93.5T800-480q0-133-93.5-226.5T480-800q-133 0-226.5 93.5T160-480q0 133 93.5 226.5T480-160Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--time-add" viewBox="0 -960 960 960"><path d="M440-120q-75 0-140.5-28T185-225q-49-49-77-114.5T80-480q0-75 28-140.5T185-735q49-49 114.5-77T440-840q21 0 40.5 2.5T520-830v82q-20-6-39.5-9t-40.5-3q-118 0-199 81t-81 199q0 118 81 199t199 81q118 0 199-81t81-199q0-11-1-20t-3-20h82q2 11 2 20v20q0 75-28 140.5T695-225q-49 49-114.5 77T440-120Zm112-192L400-464v-216h80v184l128 128-56 56Zm168-288v-120H600v-80h120v-120h80v120h120v80H800v120h-80Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--toggle-minus" viewBox="0 -960 960 960"><path d="M280-440h400v-80H280v80Zm-80 320q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm0-80h560v-560H200v560Zm0-560v560-560Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--toggle-plus" viewBox="0 -960 960 960"><path d="M440-280h80v-160h160v-80H520v-160h-80v160H280v80h160v160ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm0-80h560v-560H200v560Zm0-560v560-560Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--unknown" viewBox="0 -960 960 960"><path d="M478-240q21 0 35.5-14.5T528-290q0-21-14.5-35.5T478-340q-21 0-35.5 14.5T428-290q0 21 14.5 35.5T478-240Zm-36-154h74q0-33 7.5-52t42.5-52q26-26 41-49.5t15-56.5q0-56-41-86t-97-30q-57 0-92.5 30T342-618l66 26q5-18 22.5-39t53.5-21q32 0 48 17.5t16 38.5q0 20-12 37.5T506-526q-44 39-54 59t-10 73Zm38 314q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--unlock" viewBox="0 -960 960 960"><path d="M240-640h360v-80q0-50-35-85t-85-35q-50 0-85 35t-35 85h-80q0-83 58.5-141.5T480-920q83 0 141.5 58.5T680-720v80h40q33 0 56.5 23.5T800-560v400q0 33-23.5 56.5T720-80H240q-33 0-56.5-23.5T160-160v-400q0-33 23.5-56.5T240-640Zm0 480h480v-400H240v400Zm240-120q33 0 56.5-23.5T560-360q0-33-23.5-56.5T480-440q-33 0-56.5 23.5T400-360q0 33 23.5 56.5T480-280ZM240-160v-400 400Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--unwatch" viewBox="0 -960 960 960"><path d="m644-428-58-58q9-47-27-88t-93-32l-58-58q17-8 34.5-12t37.5-4q75 0 127.5 52.5T660-500q0 20-4 37.5T644-428Zm128 126-58-56q38-29 67.5-63.5T832-500q-50-101-143.5-160.5T480-720q-29 0-57 4t-55 12l-62-62q41-17 84-25.5t90-8.5q151 0 269 83.5T920-500q-23 59-60.5 109.5T772-302Zm20 246L624-222q-35 11-70.5 16.5T480-200q-151 0-269-83.5T40-500q21-53 53-98.5t73-81.5L56-792l56-56 736 736-56 56ZM222-624q-29 26-53 57t-41 67q50 101 143.5 160.5T480-280q20 0 39-2.5t39-5.5l-36-38q-11 3-21 4.5t-21 1.5q-75 0-127.5-52.5T300-500q0-11 1.5-21t4.5-21l-84-82Zm319 93Zm-151 75Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--user" viewBox="0 -960 960 960"><path d="M480-480q-66 0-113-47t-47-113q0-66 47-113t113-47q66 0 113 47t47 113q0 66-47 113t-113 47ZM160-160v-112q0-34 17.5-62.5T224-378q62-31 126-46.5T480-440q66 0 130 15.5T736-378q29 15 46.5 43.5T800-272v112H160Zm80-80h480v-32q0-11-5.5-20T700-306q-54-27-109-40.5T480-360q-56 0-111 13.5T260-306q-9 5-14.5 14t-5.5 20v32Zm240-320q33 0 56.5-23.5T560-640q0-33-23.5-56.5T480-720q-33 0-56.5 23.5T400-640q0 33 23.5 56.5T480-560Zm0-80Zm0 400Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--warning" viewBox="0 -960 960 960"><path d="m40-120 440-760 440 760H40Zm138-80h604L480-720 178-200Zm302-40q17 0 28.5-11.5T520-280q0-17-11.5-28.5T480-320q-17 0-28.5 11.5T440-280q0 17 11.5 28.5T480-240Zm-40-120h80v-200h-80v200Zm40-100Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--watch" viewBox="0 -960 960 960"><path d="M480-320q75 0 127.5-52.5T660-500q0-75-52.5-127.5T480-680q-75 0-127.5 52.5T300-500q0 75 52.5 127.5T480-320Zm0-72q-45 0-76.5-31.5T372-500q0-45 31.5-76.5T480-608q45 0 76.5 31.5T588-500q0 45-31.5 76.5T480-392Zm0 192q-146 0-266-81.5T40-500q54-137 174-218.5T480-800q146 0 266 81.5T920-500q-54 137-174 218.5T480-200Zm0-300Zm0 220q113 0 207.5-59.5T832-500q-50-101-144.5-160.5T480-720q-113 0-207.5 59.5T128-500q50 101 144.5 160.5T480-280Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--webhook" viewBox="0 -960 960 960"><path d="M280-120q-83 0-141.5-58.5T80-320q0-73 45.5-127.5T240-516v83q-35 12-57.5 43T160-320q0 50 35 85t85 35q50 0 85-35t35-85v-40h235q8-9 19.5-14.5T680-380q25 0 42.5 17.5T740-320q0 25-17.5 42.5T680-260q-14 0-25.5-5.5T635-280H476q-14 69-68.5 114.5T280-120Zm400 0q-56 0-101.5-27.5T507-220h107q14 10 31 15t35 5q50 0 85-35t35-85q0-50-35-85t-85-35q-20 0-37 5.5T611-418L489-621q-21-4-35-20t-14-39q0-25 17.5-42.5T500-740q25 0 42.5 17.5T560-680v8.5q0 3.5-2 8.5l87 146q8-2 17-2.5t18-.5q83 0 141.5 58.5T880-320q0 83-58.5 141.5T680-120ZM280-260q-25 0-42.5-17.5T220-320q0-22 14-38t34-21l94-156q-29-27-45.5-64.5T300-680q0-83 58.5-141.5T500-880q83 0 141.5 58.5T700-680h-80q0-50-35-85t-85-35q-50 0-85 35t-35 85q0 43 26 75.5t66 41.5L337-338q2 5 2.5 9t.5 9q0 25-17.5 42.5T280-260Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--wiki-page" viewBox="0 -960 960 960"><path d="M280-280h280v-80H280v80Zm0-160h400v-80H280v80Zm0-160h400v-80H280v80Zm-80 480q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm0-80h560v-560H200v560Zm0-560v560-560Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--workflows" viewBox="0 -960 960 960"><path d="M600-120v-120H440v-400h-80v120H80v-320h280v120h240v-120h280v320H600v-120h-80v320h80v-120h280v320H600ZM160-760v160-160Zm520 400v160-160Zm0-400v160-160Zm0 160h120v-160H680v160Zm0 400h120v-160H680v160ZM160-600h120v-160H160v160Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--zoom-in" viewBox="0 -960 960 960"><path d="M784-120 532-372q-30 24-69 38t-83 14q-109 0-184.5-75.5T120-580q0-109 75.5-184.5T380-840q109 0 184.5 75.5T640-580q0 44-14 83t-38 69l252 252-56 56ZM380-400q75 0 127.5-52.5T560-580q0-75-52.5-127.5T380-760q-75 0-127.5 52.5T200-580q0 75 52.5 127.5T380-400Zm-40-60v-80h-80v-80h80v-80h80v80h80v80h-80v80h-80Z" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon--zoom-out" viewBox="0 -960 960 960"><path d="M784-120 532-372q-30 24-69 38t-83 14q-109 0-184.5-75.5T120-580q0-109 75.5-184.5T380-840q109 0 184.5 75.5T640-580q0 44-14 83t-38 69l252 252-56 56ZM380-400q75 0 127.5-52.5T560-580q0-75-52.5-127.5T380-760q-75 0-127.5 52.5T200-580q0 75 52.5 127.5T380-400ZM280-540v-80h200v80H280Z" fill="currentColor" stroke="none"/></symbol>
</svg>

After

Width:  |  Height:  |  Size: 44 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" width="24" height="24"><path d="M120-240v-80h720v80H120Zm0-200v-80h720v80H120Zm0-200v-80h720v80H120Z"/></svg>

After

Width:  |  Height:  |  Size: 175 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" width="24" height="24"><path fill="#cac4cf" d="M784-120 532-372q-30 24-69 38t-83 14q-109 0-184.5-75.5T120-580q0-109 75.5-184.5T380-840q109 0 184.5 75.5T640-580q0 44-14 83t-38 69l252 252-56 56ZM380-400q75 0 127.5-52.5T560-580q0-75-52.5-127.5T380-760q-75 0-127.5 52.5T200-580q0 75 52.5 127.5T380-400Z"/></svg>

After

Width:  |  Height:  |  Size: 373 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" width="24" height="24"><path fill="#49454e" d="M784-120 532-372q-30 24-69 38t-83 14q-109 0-184.5-75.5T120-580q0-109 75.5-184.5T380-840q109 0 184.5 75.5T640-580q0 44-14 83t-38 69l252 252-56 56ZM380-400q75 0 127.5-52.5T560-580q0-75-52.5-127.5T380-760q-75 0-127.5 52.5T200-580q0 75 52.5 127.5T380-400Z"/></svg>

After

Width:  |  Height:  |  Size: 373 B

+84
View File
@@ -0,0 +1,84 @@
/*
* tonalmine: per-user appearance (mode + color palette).
*
* 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
* flyout. The choice is stored per browser in localStorage.
*/
(function () {
// Ids must match PALETTES in tools/palettes/build_palettes.mjs (the build checks this).
var PALETTES = [
['purple', 'Purple', '#6750A4'],
['blue', 'Blue', '#0061A4'],
['teal', 'Teal', '#006A6A'],
['green', 'Green', '#386A20'],
['orange', 'Orange', '#8B5000']
];
var MODES = [['auto', 'Auto'], ['light', 'Light'], ['dark', 'Dark']];
var KEY = 'tonalmine';
var root = document.documentElement;
function load() {
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) {
if (prefs.palette && prefs.palette !== PALETTES[0][0]) root.setAttribute('data-palette', prefs.palette);
else root.removeAttribute('data-palette');
if (prefs.mode === 'light' || prefs.mode === 'dark') root.setAttribute('data-mode', prefs.mode);
else root.removeAttribute('data-mode');
}
var prefs = load();
apply(prefs);
function button(attr, value, label, pressed, extra) {
return '<button type="button" ' + attr + '="' + value + '" aria-pressed="' + pressed + '"' + (extra || '') + '>' + label + '</button>';
}
function pickerHtml() {
var mode = prefs.mode || 'auto';
var palette = prefs.palette || PALETTES[0][0];
return '<div class="nm-appearance">' +
'<div class="nm-appearance__title">Appearance</div>' +
'<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('') +
'</div>' +
'<div class="nm-swatches" role="group" aria-label="Color">' +
PALETTES.map(function (p) {
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] + '"');
}).join('') +
'</div></div>';
}
function sync() {
document.querySelectorAll('[data-nm-mode]').forEach(function (b) {
b.setAttribute('aria-pressed', String(b.getAttribute('data-nm-mode') === (prefs.mode || 'auto')));
});
document.querySelectorAll('[data-nm-palette]').forEach(function (b) {
b.setAttribute('aria-pressed', String(b.getAttribute('data-nm-palette') === (prefs.palette || PALETTES[0][0])));
});
}
document.addEventListener('click', function (event) {
var target = event.target.closest('[data-nm-mode], [data-nm-palette]');
if (!target) return;
if (target.hasAttribute('data-nm-mode')) prefs.mode = target.getAttribute('data-nm-mode');
else prefs.palette = target.getAttribute('data-nm-palette');
save(prefs);
apply(prefs);
sync();
});
document.addEventListener('DOMContentLoaded', function () {
var menu = document.querySelector('#account .dropdown-content .dropdown-divider');
if (menu) menu.insertAdjacentHTML('afterend', pickerHtml() + '<div class="dropdown-divider"></div>');
var flyout = document.querySelector('.js-flyout-menu');
if (flyout) flyout.insertAdjacentHTML('beforeend', pickerHtml());
});
})();
+12
View File
@@ -0,0 +1,12 @@
/* tonalmine: Material Design 3 theme for Redmine 7.0 */
@import url(/application.css);
@import url(./tokens.css);
@import url(./palettes.css);
@import url(./bridge.css);
@import url(./base.css);
@import url(./layout.css);
@import url(./components.css);
@import url(./tables.css);
@import url(./pages.css);
@import url(./typography.css);
@import url(./mobile.css);
+47
View File
@@ -0,0 +1,47 @@
@font-face {
font-family: Roboto;
font-style: normal;
font-display: swap;
font-weight: 100 900;
src: url(../fonts/roboto-latin-ext-wght-normal.woff2) format("woff2");
unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
@font-face {
font-family: Roboto;
font-style: normal;
font-display: swap;
font-weight: 100 900;
src: url(../fonts/roboto-latin-wght-normal.woff2) format("woff2");
unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
body {
background-color: var(--md-sys-color-surface);
color: var(--md-sys-color-on-surface);
font: var(--md-sys-typescale-body-medium);
letter-spacing: var(--md-sys-typescale-body-medium-tracking);
}
::selection {
background-color: var(--md-sys-color-primary-container);
color: var(--md-sys-color-on-primary-container);
}
:focus-visible {
outline: 2px solid var(--md-sys-color-primary);
outline-offset: 2px;
}
a, a:link, a:visited { color: var(--md-sys-color-primary); }
a:hover, a:active { color: var(--md-sys-color-primary); text-decoration: underline; }
h1, h2, h3, h4, h5, h6,
#content h1, #content h2, #content h3, #content h4, #content h5, #content h6 {
color: var(--md-sys-color-on-surface);
font-family: var(--md-ref-typeface-plain);
}
h2, #content h2 { font: var(--md-sys-typescale-headline-small); }
h3, #content h3 { font: var(--md-sys-typescale-title-large); }
h4, #content h4 { font: var(--md-sys-typescale-title-small); letter-spacing: var(--md-sys-typescale-title-small-tracking); }
+139
View File
@@ -0,0 +1,139 @@
/*
* Maps Redmine 7 core variables (open-color --oc-*, --fonts-main, --color-*) to M3 roles.
* Core CSS uses these ~330 times, so this file recolors most of Redmine, and because
* the roles switch per palette and mode (palettes.css), all of Redmine follows.
*
* Ramp per color family (index 0-9): 0-2 light containers, 3-5 mid tones for
* borders, 6-9 the strong role color used for text and icons.
* --oc-white is used both as background and as text on strong colors; both work with
* surface-container-lowest because strong colors are light in dark mode.
*/
:root {
--fonts-main: var(--md-ref-typeface-plain);
--color-current-marker: var(--md-sys-color-primary);
--oc-white: var(--md-sys-color-surface-container-lowest);
--oc-black: var(--md-sys-color-inverse-surface);
--oc-gray-0: var(--md-sys-color-surface-container-low);
--oc-gray-1: var(--md-sys-color-surface-container);
--oc-gray-2: var(--md-sys-color-surface-container-high);
--oc-gray-3: var(--md-sys-color-surface-container-highest);
--oc-gray-4: var(--md-sys-color-outline-variant);
--oc-gray-5: var(--md-sys-color-outline);
--oc-gray-6: var(--md-sys-color-on-surface-variant);
--oc-gray-7: var(--md-sys-color-on-surface-variant);
--oc-gray-8: var(--md-sys-color-on-surface);
--oc-gray-9: var(--md-sys-color-on-surface);
--oc-blue-0: color-mix(in srgb, var(--md-sys-color-primary-container) 35%, var(--md-sys-color-surface));
--oc-blue-1: color-mix(in srgb, var(--md-sys-color-primary-container) 65%, var(--md-sys-color-surface));
--oc-blue-2: var(--md-sys-color-primary-container);
--oc-blue-3: color-mix(in srgb, var(--md-sys-color-primary) 35%, var(--md-sys-color-primary-container));
--oc-blue-4: color-mix(in srgb, var(--md-sys-color-primary) 60%, var(--md-sys-color-primary-container));
--oc-blue-5: color-mix(in srgb, var(--md-sys-color-primary) 80%, var(--md-sys-color-primary-container));
--oc-blue-6: var(--md-sys-color-primary);
--oc-blue-7: var(--md-sys-color-primary);
--oc-blue-8: var(--md-sys-color-primary);
--oc-blue-9: var(--md-sys-color-primary);
--oc-indigo-0: color-mix(in srgb, var(--md-sys-color-primary-container) 35%, var(--md-sys-color-surface));
--oc-indigo-1: color-mix(in srgb, var(--md-sys-color-primary-container) 65%, var(--md-sys-color-surface));
--oc-indigo-2: var(--md-sys-color-primary-container);
--oc-indigo-3: color-mix(in srgb, var(--md-sys-color-primary) 35%, var(--md-sys-color-primary-container));
--oc-indigo-4: color-mix(in srgb, var(--md-sys-color-primary) 60%, var(--md-sys-color-primary-container));
--oc-indigo-5: color-mix(in srgb, var(--md-sys-color-primary) 80%, var(--md-sys-color-primary-container));
--oc-indigo-6: var(--md-sys-color-primary);
--oc-indigo-7: var(--md-sys-color-primary);
--oc-indigo-8: var(--md-sys-color-primary);
--oc-indigo-9: var(--md-sys-color-primary);
--oc-cyan-0: color-mix(in srgb, var(--md-sys-color-primary-container) 35%, var(--md-sys-color-surface));
--oc-cyan-1: color-mix(in srgb, var(--md-sys-color-primary-container) 65%, var(--md-sys-color-surface));
--oc-cyan-2: var(--md-sys-color-primary-container);
--oc-cyan-3: color-mix(in srgb, var(--md-sys-color-primary) 35%, var(--md-sys-color-primary-container));
--oc-cyan-4: color-mix(in srgb, var(--md-sys-color-primary) 60%, var(--md-sys-color-primary-container));
--oc-cyan-5: color-mix(in srgb, var(--md-sys-color-primary) 80%, var(--md-sys-color-primary-container));
--oc-cyan-6: var(--md-sys-color-primary);
--oc-cyan-7: var(--md-sys-color-primary);
--oc-cyan-8: var(--md-sys-color-primary);
--oc-cyan-9: var(--md-sys-color-primary);
--oc-violet-0: color-mix(in srgb, var(--md-sys-color-primary-container) 35%, var(--md-sys-color-surface));
--oc-violet-1: color-mix(in srgb, var(--md-sys-color-primary-container) 65%, var(--md-sys-color-surface));
--oc-violet-2: var(--md-sys-color-primary-container);
--oc-violet-3: color-mix(in srgb, var(--md-sys-color-primary) 35%, var(--md-sys-color-primary-container));
--oc-violet-4: color-mix(in srgb, var(--md-sys-color-primary) 60%, var(--md-sys-color-primary-container));
--oc-violet-5: color-mix(in srgb, var(--md-sys-color-primary) 80%, var(--md-sys-color-primary-container));
--oc-violet-6: var(--md-sys-color-primary);
--oc-violet-7: var(--md-sys-color-primary);
--oc-violet-8: var(--md-sys-color-primary);
--oc-violet-9: var(--md-sys-color-primary);
--oc-grape-0: color-mix(in srgb, var(--md-sys-color-primary-container) 35%, var(--md-sys-color-surface));
--oc-grape-1: color-mix(in srgb, var(--md-sys-color-primary-container) 65%, var(--md-sys-color-surface));
--oc-grape-2: var(--md-sys-color-primary-container);
--oc-grape-3: color-mix(in srgb, var(--md-sys-color-primary) 35%, var(--md-sys-color-primary-container));
--oc-grape-4: color-mix(in srgb, var(--md-sys-color-primary) 60%, var(--md-sys-color-primary-container));
--oc-grape-5: color-mix(in srgb, var(--md-sys-color-primary) 80%, var(--md-sys-color-primary-container));
--oc-grape-6: var(--md-sys-color-primary);
--oc-grape-7: var(--md-sys-color-primary);
--oc-grape-8: var(--md-sys-color-primary);
--oc-grape-9: var(--md-sys-color-primary);
--oc-pink-0: color-mix(in srgb, var(--md-sys-color-tertiary-container) 35%, var(--md-sys-color-surface));
--oc-pink-1: color-mix(in srgb, var(--md-sys-color-tertiary-container) 65%, var(--md-sys-color-surface));
--oc-pink-2: var(--md-sys-color-tertiary-container);
--oc-pink-3: color-mix(in srgb, var(--md-sys-color-tertiary) 35%, var(--md-sys-color-tertiary-container));
--oc-pink-4: color-mix(in srgb, var(--md-sys-color-tertiary) 60%, var(--md-sys-color-tertiary-container));
--oc-pink-5: color-mix(in srgb, var(--md-sys-color-tertiary) 80%, var(--md-sys-color-tertiary-container));
--oc-pink-6: var(--md-sys-color-tertiary);
--oc-pink-7: var(--md-sys-color-tertiary);
--oc-pink-8: var(--md-sys-color-tertiary);
--oc-pink-9: var(--md-sys-color-tertiary);
--oc-red-0: color-mix(in srgb, var(--md-sys-color-error-container) 35%, var(--md-sys-color-surface));
--oc-red-1: color-mix(in srgb, var(--md-sys-color-error-container) 65%, var(--md-sys-color-surface));
--oc-red-2: var(--md-sys-color-error-container);
--oc-red-3: color-mix(in srgb, var(--md-sys-color-error) 35%, var(--md-sys-color-error-container));
--oc-red-4: color-mix(in srgb, var(--md-sys-color-error) 60%, var(--md-sys-color-error-container));
--oc-red-5: color-mix(in srgb, var(--md-sys-color-error) 80%, var(--md-sys-color-error-container));
--oc-red-6: var(--md-sys-color-error);
--oc-red-7: var(--md-sys-color-error);
--oc-red-8: var(--md-sys-color-error);
--oc-red-9: var(--md-sys-color-error);
--oc-green-0: color-mix(in srgb, var(--md-sys-color-success-container) 35%, var(--md-sys-color-surface));
--oc-green-1: color-mix(in srgb, var(--md-sys-color-success-container) 65%, var(--md-sys-color-surface));
--oc-green-2: var(--md-sys-color-success-container);
--oc-green-3: color-mix(in srgb, var(--md-sys-color-success) 35%, var(--md-sys-color-success-container));
--oc-green-4: color-mix(in srgb, var(--md-sys-color-success) 60%, var(--md-sys-color-success-container));
--oc-green-5: color-mix(in srgb, var(--md-sys-color-success) 80%, var(--md-sys-color-success-container));
--oc-green-6: var(--md-sys-color-success);
--oc-green-7: var(--md-sys-color-success);
--oc-green-8: var(--md-sys-color-success);
--oc-green-9: var(--md-sys-color-success);
--oc-yellow-0: color-mix(in srgb, var(--md-sys-color-warning-container) 35%, var(--md-sys-color-surface));
--oc-yellow-1: color-mix(in srgb, var(--md-sys-color-warning-container) 65%, var(--md-sys-color-surface));
--oc-yellow-2: var(--md-sys-color-warning-container);
--oc-yellow-3: color-mix(in srgb, var(--md-sys-color-warning) 35%, var(--md-sys-color-warning-container));
--oc-yellow-4: color-mix(in srgb, var(--md-sys-color-warning) 60%, var(--md-sys-color-warning-container));
--oc-yellow-5: color-mix(in srgb, var(--md-sys-color-warning) 80%, var(--md-sys-color-warning-container));
--oc-yellow-6: var(--md-sys-color-warning);
--oc-yellow-7: var(--md-sys-color-warning);
--oc-yellow-8: var(--md-sys-color-warning);
--oc-yellow-9: var(--md-sys-color-warning);
--oc-orange-0: color-mix(in srgb, var(--md-sys-color-warning-container) 35%, var(--md-sys-color-surface));
--oc-orange-1: color-mix(in srgb, var(--md-sys-color-warning-container) 65%, var(--md-sys-color-surface));
--oc-orange-2: var(--md-sys-color-warning-container);
--oc-orange-3: color-mix(in srgb, var(--md-sys-color-warning) 35%, var(--md-sys-color-warning-container));
--oc-orange-4: color-mix(in srgb, var(--md-sys-color-warning) 60%, var(--md-sys-color-warning-container));
--oc-orange-5: color-mix(in srgb, var(--md-sys-color-warning) 80%, var(--md-sys-color-warning-container));
--oc-orange-6: var(--md-sys-color-warning);
--oc-orange-7: var(--md-sys-color-warning);
--oc-orange-8: var(--md-sys-color-warning);
--oc-orange-9: var(--md-sys-color-warning);
}
+557
View File
@@ -0,0 +1,557 @@
/*
* Components: buttons, text fields, cards/boxes, banners, tabs, badges,
* pagination, progress, tooltips, dialogs, date picker, autocomplete.
* Uses --nm-state-* from layout.css.
*/
/***** Buttons *****/
input[type="submit"], input[type="button"], input[type="reset"], button[type="submit"] {
block-size: 40px;
padding-block: 0;
padding-inline: 24px;
margin-block: 4px;
border: 1px solid var(--md-sys-color-outline);
border-radius: var(--md-sys-shape-corner-full);
background-color: transparent;
color: var(--md-sys-color-primary);
font: var(--md-sys-typescale-label-large);
letter-spacing: var(--md-sys-typescale-label-large-tracking);
cursor: pointer;
transition: var(--nm-transition), box-shadow var(--md-sys-motion-duration-short4) var(--md-sys-motion-easing-standard);
}
/* Filled: the main action of a form */
input[type="submit"], button[type="submit"] {
border-color: transparent;
background-color: var(--md-sys-color-primary);
color: var(--md-sys-color-on-primary);
}
/* Tonal: "Create and continue" and similar secondary submits */
input[type="submit"][name="continue"], input[type="submit"][name="follow"] {
background-color: var(--md-sys-color-secondary-container);
color: var(--md-sys-color-on-secondary-container);
}
input[type="submit"]:hover, input[type="button"]:hover, input[type="reset"]:hover, button[type="submit"]:hover {
background-image: var(--nm-state-hover);
}
input[type="submit"]:hover, button[type="submit"]:hover { background-color: var(--md-sys-color-primary); box-shadow: var(--md-sys-elevation-level1); }
input[type="submit"][name="continue"]:hover, input[type="submit"][name="follow"]:hover { background-color: var(--md-sys-color-secondary-container); }
input[type="submit"]:active, input[type="button"]:active, button[type="submit"]:active { background-image: var(--nm-state-pressed); box-shadow: none; }
input[type="submit"]:disabled, input[type="button"]:disabled, button:disabled {
background-color: color-mix(in srgb, var(--md-sys-color-on-surface) var(--md-sys-state-disabled-container-opacity), transparent);
border-color: transparent;
color: color-mix(in srgb, var(--md-sys-color-on-surface) var(--md-sys-state-disabled-content-opacity), transparent);
box-shadow: none;
cursor: default;
}
/***** Text fields (outlined) *****/
input[type="text"], input[type="password"], input[type="email"], input[type="url"], input[type="number"],
input[type="date"], input[type="search"], input[type="tel"], input:not([type]), select, textarea {
min-block-size: 40px;
padding-block: 8px;
padding-inline: 12px;
border: 1px solid var(--md-sys-color-outline);
border-radius: var(--md-sys-shape-corner-extra-small);
background-color: transparent;
color: var(--md-sys-color-on-surface);
font: var(--md-sys-typescale-body-medium);
letter-spacing: var(--md-sys-typescale-body-medium-tracking);
transition: border-color var(--md-sys-motion-duration-short4) var(--md-sys-motion-easing-standard);
}
input[type="text"]:hover, input[type="password"]:hover, input[type="email"]:hover, input[type="url"]:hover,
input[type="number"]:hover, input[type="date"]:hover, input[type="search"]:hover, select:hover, textarea:hover {
border-color: var(--md-sys-color-on-surface);
}
input[type="text"]:focus, input[type="text"]:active,
input[type="password"]:focus, input[type="password"]:active,
input[type="email"]:focus, input[type="url"]:focus, input[type="search"]:focus,
input[type="date"]:focus, input[type="date"]:active,
input[type="number"]:focus, input[type="number"]:active,
select:focus, select:active,
textarea:focus, textarea:active {
border-color: var(--md-sys-color-primary);
outline: 1px solid var(--md-sys-color-primary); /* 1px border + 1px outline = M3's 2px focused outline without layout shift */
outline-offset: -2px;
}
select {
background-color: transparent;
background-image: url(../images/arrow-drop-down.svg);
background-position: calc(100% - 6px) 50%;
background-size: 24px;
padding-inline-end: 32px;
}
[dir="rtl"] select { background-position: 6px 50%; }
/* The chevron is an image, so it cannot use color roles: one file per mode */
:root[data-mode="dark"] select { background-image: url(../images/arrow-drop-down-dark.svg); }
@media (prefers-color-scheme: dark) { :root:not([data-mode="light"]) select { background-image: url(../images/arrow-drop-down-dark.svg); } }
/* Autocomplete fields (parent task, watchers): M3 leading search icon, same per-mode files */
input.autocomplete {
background: transparent url(../images/search.svg) no-repeat 12px 50% / 20px;
padding-inline-start: 40px !important; /* core sets 20px with !important */
}
[dir="rtl"] input.autocomplete { background-position: calc(100% - 12px) 50%; }
/* :where() keeps the mode swap at the base rule's specificity, so core's own cases still win:
the jump box search (own icon, no background) and the ajax-loading spinner */
: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); } }
select[multiple] { background-image: none; padding-inline-end: 12px; }
input::placeholder, textarea::placeholder { color: var(--md-sys-color-on-surface-variant); opacity: 1; }
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"]::file-selector-button {
block-size: 40px;
margin-inline-end: 12px;
padding-inline: 24px;
border: 0;
border-radius: var(--md-sys-shape-corner-full);
background-color: var(--md-sys-color-secondary-container);
color: var(--md-sys-color-on-secondary-container);
font: var(--md-sys-typescale-label-large);
cursor: pointer;
}
input:disabled, select:disabled, textarea:disabled {
border-color: color-mix(in srgb, var(--md-sys-color-on-surface) var(--md-sys-state-disabled-container-opacity), transparent);
color: color-mix(in srgb, var(--md-sys-color-on-surface) var(--md-sys-state-disabled-content-opacity), transparent);
}
/***** Cards: .box, tabular forms, fieldsets *****/
.box {
padding: 16px;
margin-block-end: 16px;
background-color: var(--md-sys-color-surface-container-low);
color: var(--md-sys-color-on-surface);
border: 0;
border-radius: var(--md-sys-shape-corner-medium);
line-height: 1.5;
}
/* M3 has no legend notched into a border. A fieldset on its own is a filled card with the
legend as headline inside. A fieldset inside a card or form, and a collapsible one, is a
section: a full-width divider with the legend as subheader below it (M3 divider guidance). */
fieldset {
padding: 16px;
margin-block-end: 16px;
border: 0;
border-radius: var(--md-sys-shape-corner-medium);
background-color: var(--md-sys-color-surface-container-low);
}
:is(.box, fieldset) fieldset, fieldset.collapsible:not(.box) {
padding: 8px 0 0;
margin-block: 8px;
border: 0;
border-block-start: 1px solid var(--md-sys-color-outline-variant);
border-radius: 0;
background-color: transparent;
}
/* A section that opens its card needs no divider: the card edge already separates it */
:is(.box, fieldset) > fieldset:first-child { margin-block-start: 0; padding-block-start: 0; border-block-start: 0; }
/* Floated, the legend leaves the border and flows as the first line of the fieldset */
fieldset > legend {
float: inline-start;
inline-size: 100%;
padding: 0 0 8px;
color: var(--md-sys-color-on-surface-variant);
font: var(--md-sys-typescale-title-small);
letter-spacing: var(--md-sys-typescale-title-small-tracking);
}
fieldset > legend ~ * { clear: inline-start; }
fieldset.collapsed > legend { padding-block-end: 0; }
/* The chevron stays with a label that wraps */
fieldset.collapsible > legend { display: flex; align-items: center; gap: 4px; }
.tabular label { color: var(--md-sys-color-on-surface-variant); font-weight: 500; }
.tabular p { padding-block: 4px; }
/* Center the 24px label line on the 40px fields next to it */
.tabular label:not(.floating):not(.block):not(.inline):not(.no-css) { padding-block-start: 8px; }
/* Checkboxes and radios placed directly in a row align with that label line too */
/* Checkboxes and radios next to such a label align with its text line */
.tabular p:has(> label:not(.floating):not(.block):not(.inline):not(.no-css)) > input:is([type="checkbox"], [type="radio"]) { margin-block: 13px 0; }
/* Rows without a field at the label's height start at the top: checkbox lists (watchers)
and the issue description, whose editor stays hidden until "Edit" is clicked */
.tabular p:is(:has(label.floating), :has(> #issue_description_and_toolbar[style*="none"])) > label:not(.floating):not(.block):not(.inline):not(.no-css) { padding-block-start: 0; }
hr { background: var(--md-sys-color-outline-variant); }
blockquote { border-inline-start: 4px solid var(--md-sys-color-outline-variant); color: var(--md-sys-color-on-surface-variant); }
/* 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 */
.tabular :is(input, select) { max-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,
textarea#custom_field_possible_values, textarea#custom_field_default_value,
input#issue_subject, input#document_title, input#content_comments, input#time_entry_comments,
.tabular .jstTabs, .tabular .wiki-preview, .jstBlock .jstTabs, .jstBlock .wiki-preview { inline-size: 100%; box-sizing: border-box; }
/* An invalid field keeps the error colour while focused */
.tabular label.error + :is(input, select, textarea):focus {
border-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) */
.tabular p:not(:has(input:not([type=hidden]), select, textarea)) > label:not(.floating):not(.block):not(.inline):not(.no-css) { padding-block-start: 0; }
/* Cancel next to a submit button */
input[type="submit"] + a { margin-inline-start: 8px; }
/* Checkbox lists in forms (custom field values, notified projects) */
.check_box_group {
padding-block: 4px;
padding-inline: 8px;
border-color: var(--md-sys-color-outline);
border-radius: var(--md-sys-shape-corner-extra-small);
background-color: transparent;
}
/***** Banners: flash messages, errors, warnings, empty states *****/
#errorExplanation, div.flash, .nodata, .warning, .conflict {
border: 0;
border-radius: var(--md-sys-shape-corner-medium);
padding-block: 14px;
padding-inline: 48px 16px;
font: var(--md-sys-typescale-body-large);
letter-spacing: var(--md-sys-typescale-body-large-tracking);
}
div.flash svg.icon-svg, #errorExplanation svg.icon-svg, .conflict svg.icon-svg { margin-inline: -32px 8px; }
div.flash.error, #errorExplanation {
background-color: var(--md-sys-color-error-container);
color: var(--md-sys-color-on-error-container);
}
div.flash.notice {
background-color: var(--md-sys-color-success-container);
color: var(--md-sys-color-on-success-container);
}
div.flash.warning, .conflict {
background-color: var(--md-sys-color-warning-container);
color: var(--md-sys-color-on-warning-container);
}
.nodata, .warning {
padding-inline: 16px;
background-color: var(--md-sys-color-surface-container);
color: var(--md-sys-color-on-surface-variant);
}
div.flash.error svg.icon-svg, #errorExplanation svg.icon-svg,
div.flash.notice svg.icon-svg, div.flash.warning svg.icon-svg, .conflict svg.icon-svg { stroke: currentColor; }
/***** Secondary tabs (settings, history, admin) *****/
#content .tabs { block-size: 48px; margin-block-end: 16px; }
#content .tabs ul { padding-inline-start: 0; border-block-end: 1px solid var(--md-sys-color-outline-variant); }
#content .tabs ul li { margin-inline-end: 0; margin-block-end: 0; }
#content .tabs ul li a, #content .tabs ul li a:hover {
position: relative;
padding-block: 14px;
padding-inline: 16px;
border: 0;
border-radius: 0;
background-color: transparent;
box-shadow: none;
color: var(--md-sys-color-on-surface-variant);
font: var(--md-sys-typescale-title-small);
letter-spacing: var(--md-sys-typescale-title-small-tracking);
}
#content .tabs ul li a:hover { background-image: var(--nm-state-hover); color: var(--md-sys-color-on-surface); }
#content .tabs ul li a.selected, #content .tabs ul li a.selected:hover {
background-color: transparent;
border: 0;
box-shadow: inset 0 -2px 0 var(--md-sys-color-primary);
color: var(--md-sys-color-on-surface);
}
/* Scroll arrows for tabs that overflow: standard icon buttons at the end of the tab row */
#content .tabs div.tabs-buttons {
display: flex;
align-items: center;
justify-content: end;
gap: 4px;
inline-size: auto;
block-size: 48px;
padding-inline-start: 8px;
box-sizing: border-box;
background: var(--md-sys-color-surface);
border-block-end: 1px solid var(--md-sys-color-outline-variant);
}
#content .tabs button.tab-left, #content .tabs button.tab-right,
#main-menu button.tab-left, #main-menu button.tab-right {
position: static;
display: inline-flex;
align-items: center;
justify-content: center;
inline-size: 40px;
block-size: 40px;
padding: 0;
border: 0;
border-radius: var(--md-sys-shape-corner-full);
background-color: transparent;
color: var(--md-sys-color-on-surface-variant);
}
:is(#content .tabs, #main-menu) button:is(.tab-left, .tab-right) svg.icon-svg { stroke: currentColor; }
:is(#content .tabs, #main-menu) button:is(.tab-left, .tab-right):hover { background-image: var(--nm-state-hover); }
:is(#content .tabs, #main-menu) button:is(.tab-left, .tab-right).disabled {
background: transparent;
color: color-mix(in srgb, var(--md-sys-color-on-surface) var(--md-sys-state-disabled-content-opacity), transparent);
}
/***** Badges *****/
.badge {
inset-block-end: 2px;
padding-block: 2px;
padding-inline: 6px;
border-radius: var(--md-sys-shape-corner-small);
font: var(--md-sys-typescale-label-small);
letter-spacing: var(--md-sys-typescale-label-small-tracking);
}
.badge-private { background: var(--md-sys-color-error); border-color: var(--md-sys-color-error); color: var(--md-sys-color-on-error); }
.badge-count, .badge-issues-count {
border-radius: var(--md-sys-shape-corner-full);
background: var(--md-sys-color-secondary-container);
color: var(--md-sys-color-on-secondary-container);
}
.badge-status-open { border-color: var(--md-sys-color-primary); color: var(--md-sys-color-primary); }
.badge-status-closed { border-color: var(--md-sys-color-success); color: var(--md-sys-color-success); }
.badge-status-locked { border-color: var(--md-sys-color-outline); color: var(--md-sys-color-on-surface-variant); }
/***** Pagination *****/
span.pagination { margin-inline-start: 0; color: var(--md-sys-color-on-surface-variant); }
.pagination ul.pages { margin-inline: 0 8px; }
.pagination ul.pages li,
.pagination ul.pages li:first-child,
.pagination ul.pages li:last-child {
margin-inline-end: 4px;
border: 0;
border-radius: var(--md-sys-shape-corner-full);
line-height: 40px;
min-inline-size: 40px;
}
.pagination ul.pages li a, .pagination ul.pages li span {
display: block;
padding-block: 0;
padding-inline: 12px;
border-radius: var(--md-sys-shape-corner-full);
color: var(--md-sys-color-on-surface-variant);
}
.pagination ul.pages li.page:hover { background-color: transparent; background-image: var(--nm-state-hover); }
.pagination ul.pages li.page a:hover { color: var(--md-sys-color-on-surface); text-decoration: none; }
.pagination ul.pages li.current {
background-color: var(--md-sys-color-secondary-container);
color: var(--md-sys-color-on-secondary-container);
font-weight: 500;
}
.pagination ul.pages li.current span { color: inherit; }
/***** Linear progress *****/
table.progress { border-radius: var(--md-sys-shape-corner-full); overflow: hidden; }
table.progress td { block-size: 4px; padding: 0; }
.version-overview table.progress td { block-size: 8px; }
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.todo { background: var(--md-sys-color-secondary-container); }
/***** Tooltips (plain) *****/
.ui-tooltip {
background: var(--md-sys-color-inverse-surface);
color: var(--md-sys-color-inverse-on-surface);
border-radius: var(--md-sys-shape-corner-extra-small);
font: var(--md-sys-typescale-body-small);
letter-spacing: var(--md-sys-typescale-body-small-tracking);
padding-block: 4px;
padding-inline: 8px;
}
/***** 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-content, .ui-widget-content {
background: var(--md-sys-color-surface-container-high);
border-color: var(--md-sys-color-outline-variant);
color: var(--md-sys-color-on-surface);
}
.ui-widget-content a { color: var(--md-sys-color-primary); }
.ui-dialog.ui-widget.ui-widget-content {
padding: 8px;
border: 0;
border-radius: var(--md-sys-shape-corner-extra-large);
box-shadow: var(--md-sys-elevation-level3);
}
.ui-widget-header {
background: transparent;
border: 0;
color: var(--md-sys-color-on-surface);
}
.ui-dialog .ui-dialog-titlebar { padding-block: 16px 8px; padding-inline: 16px; }
.ui-dialog .ui-dialog-title { font: var(--md-sys-typescale-headline-small); }
.ui-dialog .ui-dialog-content { padding-inline: 16px; }
.ui-dialog .ui-dialog-titlebar-close {
inline-size: 40px;
block-size: 40px;
margin-block-start: -20px;
border: 0;
border-radius: var(--md-sys-shape-corner-full);
background: transparent;
}
.ui-widget-overlay { background: var(--md-sys-color-scrim); opacity: 32%; }
.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default {
background: transparent;
border-color: transparent;
color: var(--md-sys-color-on-surface);
border-radius: var(--md-sys-shape-corner-full);
}
.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus {
background: color-mix(in srgb, var(--md-sys-color-on-surface) var(--md-sys-state-hover-state-layer-opacity), transparent);
border-color: transparent;
color: var(--md-sys-color-on-surface);
}
.ui-state-highlight, .ui-widget-content .ui-state-highlight {
background: transparent;
border: 1px solid var(--md-sys-color-primary);
color: var(--md-sys-color-primary);
}
.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active {
background: var(--md-sys-color-primary);
border-color: var(--md-sys-color-primary);
color: var(--md-sys-color-on-primary);
}
.ui-datepicker { padding: 12px; border-radius: var(--md-sys-shape-corner-large); box-shadow: var(--md-sys-elevation-level3); }
.ui-datepicker th { color: var(--md-sys-color-on-surface-variant); font: var(--md-sys-typescale-body-small); }
.ui-datepicker td a, .ui-datepicker td span { text-align: center; padding: 6px; }
.ui-autocomplete, .ui-menu, .tribute-container ul {
background: var(--md-sys-color-surface-container);
border: 0;
border-radius: var(--md-sys-shape-corner-extra-small);
box-shadow: var(--md-sys-elevation-level2);
color: var(--md-sys-color-on-surface);
padding-block: 8px;
}
.ui-menu .ui-menu-item-wrapper.ui-state-active, .tribute-container li.highlight {
margin: 0;
background: var(--md-sys-color-secondary-container);
border-color: transparent;
color: var(--md-sys-color-on-secondary-container);
}
/***** Icons *****/
/* The theme sprite (images/icons.svg) draws Material Symbols with fill="currentColor",
so icon color comes from `color`. Core's `stroke` rules still style plugin icons. */
svg.icon-svg { color: var(--md-sys-color-on-surface-variant); }
a.icon .icon-svg, a.icon-only .icon-svg, span.icon-actions .icon-svg, a .icon-svg { color: var(--md-sys-color-primary); }
a.icon:hover .icon-svg, a.icon-only:hover .icon-svg, span.icon-actions:hover .icon-svg { color: var(--md-sys-color-on-surface); }
.icon-ok svg.icon-svg { color: var(--md-sys-color-success); }
.icon-error svg.icon-svg { color: var(--md-sys-color-error); }
.icon-warning svg.icon-svg { color: var(--md-sys-color-warning); }
div.flash svg.icon-svg, #errorExplanation svg.icon-svg, .conflict svg.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; }
html .context-menu-selection svg.icon-svg { color: var(--md-sys-color-on-secondary-container) !important; }
/***** Wiki toolbar (jstoolbar.css loads after the theme) *****/
/* Core draws the buttons as fixed-color background images, unreadable in dark mode.
The same SVGs are used as masks here, filled with currentColor. */
html .jstElements { block-size: auto; padding-block-end: 4px; }
html .jstElements button {
position: relative;
inline-size: 32px;
block-size: 32px;
margin-inline-end: 0;
padding: 0;
border: 0;
border-radius: var(--md-sys-shape-corner-full);
background-color: transparent;
background-image: none;
color: var(--md-sys-color-on-surface-variant);
opacity: 1;
vertical-align: middle;
}
html .jstElements button:hover { background-color: color-mix(in srgb, currentColor var(--md-sys-state-hover-state-layer-opacity), transparent); color: var(--md-sys-color-on-surface); }
html .jstElements button::after {
content: "";
position: absolute;
inset: 7px;
background-color: currentColor;
mask: var(--nm-jst-icon) center / contain no-repeat;
}
html .jstElements button.jstb_pre::after { display: none; }
html .jstb_pre::before { color: currentColor; font: var(--md-sys-typescale-label-small); }
html .jstSpacer { margin-inline-end: 8px; }
html .jstb_strong { --nm-jst-icon: url(/jstoolbar/bold.svg); }
html .jstb_em { --nm-jst-icon: url(/jstoolbar/italic.svg); }
html .jstb_ins { --nm-jst-icon: url(/jstoolbar/underline.svg); }
html .jstb_del { --nm-jst-icon: url(/jstoolbar/strikethrough.svg); }
html .jstb_code { --nm-jst-icon: url(/jstoolbar/letter-c.svg); }
html .jstb_h1 { --nm-jst-icon: url(/jstoolbar/h1.svg); }
html .jstb_h2 { --nm-jst-icon: url(/jstoolbar/h2.svg); }
html .jstb_h3 { --nm-jst-icon: url(/jstoolbar/h3.svg); }
html .jstb_ul { --nm-jst-icon: url(/jstoolbar/list.svg); }
html .jstb_ol { --nm-jst-icon: url(/jstoolbar/list-numbers.svg); }
html .jstb_tl { --nm-jst-icon: url(/jstoolbar/list-check.svg); }
html .jstb_bq { --nm-jst-icon: url(/jstoolbar/indent-increase.svg); }
html .jstb_unbq { --nm-jst-icon: url(/jstoolbar/indent-decrease.svg); }
html .jstb_precode { --nm-jst-icon: url(/jstoolbar/code.svg); }
html .jstb_link { --nm-jst-icon: url(/jstoolbar/wiki_link.svg); }
html .jstb_img { --nm-jst-icon: url(/jstoolbar/image.svg); }
html .jstb_table { --nm-jst-icon: url(/jstoolbar/table.svg); }
html .jstb_help { --nm-jst-icon: url(/jstoolbar/help.svg); }
html .wiki-preview { background-color: transparent; color: var(--md-sys-color-on-surface); border-color: var(--md-sys-color-outline-variant); border-radius: var(--md-sys-shape-corner-extra-small); }
html .table-generator td { background-color: var(--md-sys-color-surface-container-high); border-color: var(--md-sys-color-outline); }
html .table-generator td.selected-cell, html .table-generator td:hover { background-color: var(--md-sys-color-primary-container); }
/***** Appearance picker (added by javascripts/theme.js to the profile menu and mobile flyout) *****/
.nm-appearance { padding-block: 8px 12px; padding-inline: 12px; }
.nm-appearance__title {
margin-block-end: 8px;
color: var(--md-sys-color-on-surface-variant);
font: var(--md-sys-typescale-label-medium);
letter-spacing: var(--md-sys-typescale-label-medium-tracking);
}
/* M3 segmented button */
.nm-segmented { display: flex; margin-block-end: 12px; }
.nm-segmented button {
flex: 1;
border-radius: 0;
block-size: 32px;
padding-inline: 12px;
border: 1px solid var(--md-sys-color-outline);
background: transparent;
color: var(--md-sys-color-on-surface);
font: var(--md-sys-typescale-label-large);
cursor: pointer;
}
.nm-segmented button + button { margin-inline-start: -1px; }
.nm-segmented button:first-child { border-start-start-radius: var(--md-sys-shape-corner-full); border-end-start-radius: var(--md-sys-shape-corner-full); }
.nm-segmented button:last-child { border-start-end-radius: var(--md-sys-shape-corner-full); border-end-end-radius: var(--md-sys-shape-corner-full); }
.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); }
/* Color swatches: the selected one gets a ring */
.nm-swatches { display: flex; gap: 8px; }
.nm-swatch {
inline-size: 28px;
block-size: 28px;
padding: 0;
border: 0;
border-radius: var(--md-sys-shape-corner-full);
background: var(--nm-swatch);
cursor: pointer;
}
.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); }
html .flyout-menu .nm-appearance { padding-inline: 16px; }
html .flyout-menu .nm-appearance__title { color: var(--md-sys-color-on-surface-variant); }
/***** Loading indicator: an M3 snackbar-style chip on inverse surface *****/
#ajax-indicator {
inline-size: auto;
padding-block: 12px;
padding-inline: 16px;
border: 0;
border-radius: var(--md-sys-shape-corner-extra-small);
background-color: var(--md-sys-color-inverse-surface);
color: var(--md-sys-color-inverse-on-surface);
box-shadow: var(--md-sys-elevation-level3);
font: var(--md-sys-typescale-body-medium);
opacity: 1;
}
/***** Code block copy button and dragged table rows *****/
div.pre-wrapper a.copy-pre-content-link {
padding: 4px;
border: 0;
border-radius: var(--md-sys-shape-corner-full);
background-color: var(--md-sys-color-surface-container-high);
color: var(--md-sys-color-on-surface-variant);
}
tr.ui-sortable-helper { border-color: var(--md-sys-color-outline-variant); box-shadow: var(--md-sys-elevation-level2); }
+332
View File
@@ -0,0 +1,332 @@
/*
* Page layout: top app bar (top menu + header), primary tabs (project menu),
* sidebar card, content, footer, menus.
*
* State layers: M3 draws hover/focus/press as a translucent layer of the content
* color. Here that is a background-image of currentColor mixed at the state opacity,
* so it works on top of any background color.
*/
:root {
--nm-state-hover: linear-gradient(color-mix(in srgb, currentColor var(--md-sys-state-hover-state-layer-opacity), transparent) 0 0);
--nm-state-pressed: linear-gradient(color-mix(in srgb, currentColor var(--md-sys-state-pressed-state-layer-opacity), transparent) 0 0);
--nm-transition: background-color var(--md-sys-motion-duration-short4) var(--md-sys-motion-easing-standard),
color var(--md-sys-motion-duration-short4) var(--md-sys-motion-easing-standard);
}
#wrapper { background: var(--md-sys-color-surface); }
/***** Top app bar *****/
nav.top-menu {
background: var(--md-sys-color-surface-container);
color: var(--md-sys-color-on-surface-variant);
font: var(--md-sys-typescale-label-large);
letter-spacing: var(--md-sys-typescale-label-large-tracking);
padding-block: 4px;
padding-inline: 12px;
}
.top-menu__links a, .top-menu__links a:link, .top-menu__links a:visited, #account .dropdown-trigger {
color: var(--md-sys-color-on-surface-variant);
border-radius: var(--md-sys-shape-corner-full);
padding-block: 6px;
padding-inline: 12px;
transition: var(--nm-transition);
}
.top-menu__links a:hover, #account .dropdown-trigger:hover {
color: var(--md-sys-color-on-surface);
background-color: transparent;
background-image: var(--nm-state-hover);
}
.top-menu__links svg.icon-svg, #account .dropdown-trigger .icon-svg,
.top-menu__links a:hover svg.icon-svg, #account .dropdown-trigger:hover .icon-svg {
stroke: currentColor;
}
#header {
background: var(--md-sys-color-surface-container);
color: var(--md-sys-color-on-surface);
padding-block: 4px 0;
padding-inline: 24px;
}
body.has-main-menu #header { padding-block-end: 48px; }
#header a, #header a:hover { color: var(--md-sys-color-on-surface); }
#header h1 {
font: var(--md-sys-typescale-title-large);
min-block-size: 3rem;
}
#header h1 .breadcrumbs {
font: var(--md-sys-typescale-label-medium);
letter-spacing: var(--md-sys-typescale-label-medium-tracking);
color: var(--md-sys-color-on-surface-variant);
}
#header h1 .breadcrumbs a { color: var(--md-sys-color-on-surface-variant); }
#quick-search { min-block-size: 3rem; color: var(--md-sys-color-on-surface-variant); }
#quick-search #q {
inline-size: 220px;
block-size: 40px;
padding-inline: 16px;
border: 0;
border-radius: var(--md-sys-shape-corner-full);
background-color: var(--md-sys-color-surface-container-highest);
color: var(--md-sys-color-on-surface);
font: var(--md-sys-typescale-body-large);
}
#quick-search #q:focus { outline: 2px solid var(--md-sys-color-primary); outline-offset: 0; }
#quick-search form label { font: var(--md-sys-typescale-label-large); }
#project-jump.drdn { margin-inline-start: 8px; }
#project-jump .drdn-trigger {
block-size: 40px;
padding-inline: 16px 36px;
border: 1px solid var(--md-sys-color-outline-variant);
border-radius: var(--md-sys-shape-corner-full);
background-color: transparent;
color: var(--md-sys-color-on-surface);
font: var(--md-sys-typescale-label-large);
line-height: 38px;
}
#project-jump .drdn-trigger:hover { background-image: var(--nm-state-hover); }
/***** Primary tabs (project menu) *****/
#main-menu {
background: var(--md-sys-color-surface-container);
border-block-end: 1px solid var(--md-sys-color-outline-variant);
padding-block: 0;
padding-inline: 16px;
}
#main-menu ul { min-block-size: 48px; }
#main-menu li a {
position: relative;
color: var(--md-sys-color-on-surface-variant);
font: var(--md-sys-typescale-title-small);
letter-spacing: var(--md-sys-typescale-title-small-tracking);
padding-block: 14px;
padding-inline: 16px;
line-height: 20px;
transition: var(--nm-transition);
}
#main-menu li a:hover {
background-color: transparent;
background-image: var(--nm-state-hover);
color: var(--md-sys-color-on-surface);
}
#main-menu li a.selected, #main-menu li a.selected:hover {
background-color: transparent;
color: var(--md-sys-color-primary);
font-weight: 500;
box-shadow: none;
}
#main-menu li a.selected::after {
content: "";
position: absolute;
inset-inline: 16px;
inset-block-end: 0;
block-size: 3px;
border-radius: 3px 3px 0 0;
background: var(--md-sys-color-primary);
}
/* "+" new object: tonal icon button */
#main-menu li a.new-object {
display: inline-flex;
align-items: center;
justify-content: center;
inline-size: 40px;
block-size: 40px;
padding: 0;
margin-block: 0;
border: 0;
border-radius: var(--md-sys-shape-corner-medium);
background-color: var(--md-sys-color-secondary-container);
color: var(--md-sys-color-on-secondary-container);
}
#main-menu li a.new-object:hover {
background-color: var(--md-sys-color-secondary-container);
background-image: var(--nm-state-hover);
color: var(--md-sys-color-on-secondary-container);
}
/* Scroll arrows when the menu overflows: icon buttons on the bar's colour, so menu items
slide under them instead of showing through (button styles in components.css) */
#main-menu div.tabs-buttons {
display: flex;
align-items: center;
gap: 4px;
inline-size: auto;
block-size: 48px;
inset-inline-end: 0;
padding-inline: 8px;
border: 0;
background-color: var(--md-sys-color-surface-container);
}
/***** Menus: project menu children, dropdowns, profile menu *****/
#main-menu .menu-children,
html .drdn-content,
html .dropdown-content {
background-color: var(--md-sys-color-surface-container);
border: 0;
border-radius: var(--md-sys-shape-corner-extra-small);
box-shadow: var(--md-sys-elevation-level2);
color: var(--md-sys-color-on-surface);
padding-block: 8px;
overflow: hidden;
}
#main-menu .menu-children li a,
html .dropdown-content li a,
html .dropdown-content .dropdown-items a,
.drdn-items > * {
background-color: transparent;
border: 0;
border-radius: 0;
color: var(--md-sys-color-on-surface);
font: var(--md-sys-typescale-label-large);
letter-spacing: var(--md-sys-typescale-label-large-tracking);
padding-block: 10px;
padding-inline: 12px;
}
#main-menu .menu-children li a:hover,
html .dropdown-content li a:hover,
html .dropdown-content .dropdown-items a:hover,
.drdn-items > a:hover,
.drdn-items > a:focus {
background-color: transparent;
background-image: var(--nm-state-hover);
color: var(--md-sys-color-on-surface);
text-decoration: 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); }
#project-jump .drdn-items > a:hover { background-color: transparent; color: var(--md-sys-color-on-surface) !important; }
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 .user-name { color: var(--md-sys-color-on-surface); font: var(--md-sys-typescale-title-small); }
/***** Content and sidebar *****/
#main { background: var(--md-sys-color-surface); }
#content {
background-color: var(--md-sys-color-surface);
padding-block: 24px 16px;
padding-inline: 24px;
}
#sidebar {
background: var(--md-sys-color-surface-container-low);
border: 0;
border-radius: var(--md-sys-shape-corner-large);
margin-block: 16px;
margin-inline-end: 16px;
padding-block: 16px;
padding-inline: 16px;
box-sizing: border-box;
align-self: flex-start;
}
/* Collapsed: only the switch button remains, in a small rounded surface */
#main.collapsedsidebar #sidebar { inline-size: auto; margin-block: 16px; margin-inline-end: 16px; padding: 4px; }
#main.collapsedsidebar #sidebar-switch-panel { float: none; margin: 0; }
#sidebar h3 {
color: var(--md-sys-color-on-surface-variant);
font: var(--md-sys-typescale-title-small);
letter-spacing: var(--md-sys-typescale-title-small-tracking);
margin-block: 16px 8px;
}
#sidebar hr { background: var(--md-sys-color-outline-variant); margin-block: 12px; }
/* Collapse button: 32px icon button in the top corner, level with the first heading */
#sidebar-switch-panel { float: inline-end; inline-size: auto; margin-block: -6px 0; margin-inline: 8px -6px; padding: 0; }
#sidebar-switch-button {
display: flex;
align-items: center;
justify-content: center;
inline-size: 32px;
block-size: 32px;
padding: 0;
border-radius: var(--md-sys-shape-corner-full);
color: var(--md-sys-color-on-surface-variant);
}
#sidebar-switch-button svg { stroke: var(--md-sys-color-on-surface-variant); color: inherit; }
#sidebar-switch-button:hover { background-color: transparent; background-image: var(--nm-state-hover); }
/* Sidebar forms (activity, roadmap, …): label above a full-width field */
#sidebar form > p:has(> input:not([type="submit"], [type="hidden"], [type="checkbox"]), > select) {
display: flex;
flex-direction: column;
gap: 4px;
margin-block: 0 12px;
color: var(--md-sys-color-on-surface-variant);
font: var(--md-sys-typescale-label-medium);
letter-spacing: var(--md-sys-typescale-label-medium-tracking);
}
#sidebar form > p > :is(input:not([type="submit"], [type="checkbox"]), select) { inline-size: 100%; max-inline-size: none; margin: 0; }
/* First heading sits level with the collapse button, also after hidden form inputs */
#sidebar-wrapper > form:first-child > h3:first-of-type,
#sidebar-wrapper > .contextual:first-child + h3 { margin-block-start: 0; }
#sidebar-wrapper > .contextual:first-child { margin-block: 0; }
/* Watchers opening the sidebar: "Add" sits next to the collapse button, heading level with it */
#sidebar-wrapper > #watchers:first-child > .contextual { margin-block: 0; margin-inline-end: 0; }
#sidebar-wrapper > #watchers:first-child > .contextual + h3 { margin-block-start: 0; }
#sidebar form > p:empty { display: none; }
/* Checkbox lists: even 32px rows, text-colored labels */
#sidebar li:has(input[type="checkbox"]),
#sidebar :is(li, form > p) > label:has(input[type="checkbox"]),
#sidebar form > p:has(> input[type="checkbox"]) {
display: flex;
align-items: center;
gap: 12px;
min-block-size: 32px;
}
#sidebar :is(li, form > p) > label:has(input[type="checkbox"]) { flex: 1; }
#sidebar input[type="checkbox"] { inline-size: 18px; block-size: 18px; margin: 0; flex-shrink: 0; }
#sidebar li label, #sidebar li label a, #sidebar form > p > label { color: var(--md-sys-color-on-surface); font: var(--md-sys-typescale-body-medium); }
#sidebar ul + ul, #sidebar ul + p { margin-block-start: 12px; }
/* 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 */
#sidebar li > a:only-child, #admin-menu li a {
display: flex;
align-items: center;
min-block-size: 36px;
padding-inline: 12px;
border-radius: var(--md-sys-shape-corner-full);
color: var(--md-sys-color-on-surface-variant);
font: var(--md-sys-typescale-label-large);
letter-spacing: var(--md-sys-typescale-label-large-tracking);
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.selected:only-child, #admin-menu li a.selected {
margin-inline-start: 0; /* core: -6px, inset marker bar */
padding-block: 0;
box-shadow: none;
line-height: normal;
background-color: var(--md-sys-color-secondary-container);
color: var(--md-sys-color-on-secondary-container);
}
#admin-menu li { padding-block-end: 2px; }
/***** Footer *****/
#footer {
background: var(--md-sys-color-surface);
border: 0;
color: var(--md-sys-color-on-surface-variant);
font: var(--md-sys-typescale-label-small);
letter-spacing: var(--md-sys-typescale-label-small-tracking);
padding: 16px;
}
/***** Login *****/
#login-form {
inline-size: 400px;
padding: 24px;
background-color: var(--md-sys-color-surface-container-low);
border-radius: var(--md-sys-shape-corner-extra-large);
}
#login-form label { font: var(--md-sys-typescale-label-large); color: var(--md-sys-color-on-surface-variant); }
#login-form input { block-size: 40px; }
+156
View File
@@ -0,0 +1,156 @@
/*
* Mobile overrides at core's responsive.css breakpoint. The core file stays in place
* (a theme file named responsive.css would replace it); it loads after the theme,
* hence the `html` prefix on conflicting selectors.
*/
@media screen and (max-width: 899px) {
/* Top app bar */
html #header { background-color: var(--md-sys-color-surface-container); color: var(--md-sys-color-on-surface); }
html #header a.mobile-toggle-button { color: var(--md-sys-color-on-surface-variant); border-inline-start: 0; }
html #project-jump .drdn-trigger {
border: 0;
color: var(--md-sys-color-on-surface);
font: var(--md-sys-typescale-title-large);
line-height: 50px;
}
html #project-jump.drdn { padding-inline-start: 8px; }
/* Header icons: Material Symbols masks instead of core's scaled "^" and "≡" text glyphs */
html #project-jump .drdn-trigger:before {
content: '';
inset-block: 0;
margin-block: auto;
inline-size: 24px;
block-size: 24px;
padding: 0;
transform: none;
opacity: 1;
background-color: var(--md-sys-color-on-surface-variant);
mask: url(../images/arrow-drop-down.svg) center / 24px no-repeat;
}
html #header a.mobile-toggle-button {
inline-size: 48px;
block-size: 48px;
margin-block-start: 8px;
margin-inline-end: 8px;
border-radius: var(--md-sys-shape-corner-full);
line-height: normal;
}
html #header a.mobile-toggle-button:hover { background-image: var(--nm-state-hover); }
html .mobile-toggle-button:after {
content: '';
inline-size: 24px;
block-size: 24px;
margin: 12px;
background-color: currentColor;
mask: url(../images/menu.svg) center / 24px no-repeat;
}
/* Flyout: navigation drawer */
html .flyout-menu {
background-color: var(--md-sys-color-surface-container-low);
color: var(--md-sys-color-on-surface);
padding-inline: 12px;
box-sizing: border-box;
}
html .flyout-menu h3 {
block-size: auto;
margin-block: 16px 4px;
padding-inline: 16px;
border: 0;
background-color: transparent;
color: var(--md-sys-color-on-surface-variant);
font: var(--md-sys-typescale-title-small);
letter-spacing: var(--md-sys-typescale-title-small-tracking);
text-transform: none;
}
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 ul li a, html .flyout-menu ul li:first-child a {
block-size: 48px;
line-height: 48px;
margin-inline-start: 0;
padding-inline: 16px;
border: 0;
border-radius: var(--md-sys-shape-corner-full);
font: var(--md-sys-typescale-label-large);
line-height: 48px;
}
html .flyout-menu ul li a:hover { background-image: var(--nm-state-hover); }
html .flyout-menu ul li a.selected {
background-color: var(--md-sys-color-secondary-container);
color: var(--md-sys-color-on-secondary-container);
}
html .flyout-menu #watchers ul li, html .flyout-menu ul.queries li, html .flyout-menu__avatar { border-block-start: 0; }
html .flyout-menu .icon svg, html .flyout-menu .icon-only svg { color: inherit; stroke: currentColor; }
html .flyout-menu__search input[type='text'] {
block-size: 48px;
border-radius: var(--md-sys-shape-corner-full);
background-color: var(--md-sys-color-surface-container-highest);
color: var(--md-sys-color-on-surface);
padding-inline-start: 52px;
}
/* Search pill, avatar row and nav labels share one inline start: the icon and the
avatar sit where the nav labels begin (drawer padding 12px + item padding 16px) */
html .flyout-menu .flyout-menu__search { padding-inline: 0; }
html .search-magnifier--flyout { inset-inline-start: 28px; }
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); }
/* 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. */
html #content > [data-controller="quote-reply"] > .contextual {
inline-size: 100%;
margin-block-end: 8px;
padding-inline-start: 0;
white-space: normal;
}
html :is(#content > .contextual > a, #content > .contextual .drdn, #content > [data-controller="quote-reply"] > .contextual > a, p.buttons a) {
display: inline-flex;
align-items: center;
block-size: 40px;
margin-block: 4px;
margin-inline: 0 4px;
padding-block: 0;
padding-inline: 16px;
border: 1px solid var(--md-sys-color-outline);
border-radius: var(--md-sys-shape-corner-full);
box-sizing: border-box;
font: var(--md-sys-typescale-label-large);
letter-spacing: var(--md-sys-typescale-label-large-tracking);
}
/* A scrolling table takes its natural width instead of squeezing text columns to a
word per line; it scrolls inside its card */
html .autoscroll > table.list { inline-size: max-content; min-inline-size: 100%; }
/* Tables core does not wrap scroll themselves, so they no longer push the whole content
area sideways; thead and tbody share one anonymous table, so columns stay aligned.
ponytail: a table narrower than the screen would leave a gap inside its border, so small
summary tables are excluded by class; add others here if one shows a gap */
html #content table.list:not(.autoscroll > table, .issue-report) { display: block; overflow-x: auto; max-inline-size: 100%; }
/* 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:has(+ .icon-only) { inline-size: calc(100% - 36px); }
html .tabular input[type="radio"] { inline-size: auto; }
/* 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 ul { position: static; display: flex; flex-wrap: wrap; inline-size: auto; min-inline-size: 0; }
html #content .jstTabs.tabs li.tab-elements { flex: 1 1 auto; min-inline-size: 0; block-size: auto; }
html #content .jstTabs .jstElements { display: flex; flex-wrap: wrap; white-space: normal; }
html #content .jstTabs.tabs li.tab-elements::before { display: none; } /* core's full-height strut for vertical centring */
/* Subtasks and related issues: the progress bar fits its cell, the subject leaves room
for both icon buttons */
html :is(#issue_tree, #relations) td.done_ratio table.progress { inline-size: 100%; }
html :is(#issue_tree, #relations):not(.with-related-issues-table-headers) .issue:has(.buttons a) > td.subject { padding-inline-end: 72px; }
html div#version-summary { margin-block-start: 16px; }
/* Core gives the current, previous and next page a third of the row each */
html .pagination ul.pages li:is(.current, .previous, .next) { inline-size: auto; overflow: visible; }
/* The desktop sidebar card has no place on mobile; its content moves into the flyout */
#sidebar { margin: 0; border-radius: 0; }
}
+493
View File
@@ -0,0 +1,493 @@
/*
* Page-specific styles: issue view, journals, wiki content and code,
* activity, roadmap, calendar, Gantt.
*/
/***** Issue view *****/
/* .details: calendar entries are div.issue too */
div.issue.details {
background: var(--md-sys-color-surface-container-low);
border-radius: var(--md-sys-shape-corner-medium);
padding: 24px;
margin-block-end: 16px;
}
div.issue div.subject h3 { font: var(--md-sys-typescale-headline-small); }
div.issue p.author { color: var(--md-sys-color-on-surface-variant); }
div.issue .attributes .attribute .label { color: var(--md-sys-color-on-surface-variant); font-weight: 500; }
div.issue hr { background: var(--md-sys-color-outline-variant); }
div.issue.overdue .due-date .value { color: var(--md-sys-color-error); }
div.issue table.list { background: var(--md-sys-color-surface); }
/* Sticky issue header while scrolling: an M3 top app bar in its scrolled state
(surface-container, no shadow). Core hard-codes a white background. */
div#sticky-issue-header {
padding-block: 12px;
padding-inline: 24px;
border-block-end: 1px solid var(--md-sys-color-outline-variant);
background-color: var(--md-sys-color-surface-container);
box-shadow: none;
color: var(--md-sys-color-on-surface-variant);
font: var(--md-sys-typescale-title-medium);
}
div#sticky-issue-header .subject { color: var(--md-sys-color-on-surface); font-weight: 500; }
/* Progress bars inside list rows must not take the row padding and height */
table.list table.progress td, table.list table.progress td:first-child {
block-size: 4px;
padding: 0;
border: 0;
}
/***** Journals: each change is an outlined card, its header a band on top *****/
.journals .journal {
margin-block-end: 12px;
background-color: var(--md-sys-color-surface-container-low);
border: 1px solid var(--md-sys-color-outline-variant);
border-radius: var(--md-sys-shape-corner-medium);
overflow: hidden;
}
.journals h4.journal-header {
margin: 0;
background-color: var(--md-sys-color-surface-container-high);
border-block-end: 1px solid var(--md-sys-color-outline-variant);
border-radius: 0;
padding-block: 8px;
padding-inline: 16px;
font: var(--md-sys-typescale-title-small);
}
.journals h4.journal-header .update-info { color: var(--md-sys-color-on-surface-variant); font: var(--md-sys-typescale-body-small); }
.journals div:target h4.journal-header { background-color: var(--md-sys-color-secondary-container); color: var(--md-sys-color-on-secondary-container); }
.journals div.journal:has(div:target), .journals div.journal:target { border-color: var(--md-sys-color-primary); }
.journals .journal-content { margin-block-end: 0; padding-block: 4px 12px; padding-inline: 16px; }
.journals .journal-content :is(.journal-details, .wiki):last-child, .journals .journal-content .wiki > :last-child { margin-block-end: 0; }
.journals .private-notes { border-inline-start: 4px solid var(--md-sys-color-error); }
.journals .journal-details, ul.revision-info { color: var(--md-sys-color-on-surface-variant); }
/***** Reading pages: the main content sits in one filled card, like the issue details *****/
#content > div.wiki.wiki-page,
#content > div.news,
.controller-documents.action-show #content > div.wiki,
#content > [data-controller="quote-reply"] > div.message {
margin-block-end: 16px;
padding: 24px;
border-radius: var(--md-sys-shape-corner-medium);
background-color: var(--md-sys-color-surface-container-low);
}
/* The card's padding is the gap: no paragraph margin on top of it (news and messages
open with a floated reaction button) */
:is(#content > div.wiki.wiki-page, #content > div.news, .controller-documents.action-show #content > div.wiki,
#content > [data-controller="quote-reply"] > div.message) {
& > :first-child, & > .reaction + * { margin-block-start: 0; }
& > :last-child, & > .wiki:last-child > :last-child { margin-block-end: 0; }
}
/* The wiki page's actions sit above its card, not on its edge */
#content > div.wiki.wiki-page { clear: both; }
/***** Wiki content *****/
div.wiki table { padding: 0; border-collapse: separate; border-spacing: 0; border: 1px solid var(--md-sys-color-outline-variant); border-radius: var(--md-sys-shape-corner-medium); overflow: hidden; }
div.wiki td, div.wiki th { border: 0; border-block-start: 1px solid var(--md-sys-color-outline-variant); padding-block: 8px; padding-inline: 12px; }
div.wiki tr:first-child td, div.wiki tr:first-child th { border-block-start: 0; }
div.wiki th { background: var(--md-sys-color-surface-container); color: var(--md-sys-color-on-surface-variant); font: var(--md-sys-typescale-title-small); }
div.wiki a.new { color: var(--md-sys-color-error); }
/* Wiki text mostly sits in a card, so the TOC steps one tone up from it */
div.wiki ul.toc {
background-color: var(--md-sys-color-surface-container);
border: 0;
border-radius: var(--md-sys-shape-corner-medium);
padding: 12px 16px;
}
div.wiki ul.toc li.heading { color: var(--md-sys-color-on-surface-variant); font: var(--md-sys-typescale-title-small); }
div.wiki pre {
margin-inline: 0;
padding: 16px;
background-color: var(--md-sys-color-surface-container);
border: 0;
border-radius: var(--md-sys-shape-corner-medium);
}
div.wiki pre .syntaxhl, .syntaxhl { background: transparent; }
div.wiki *:not(pre) > code, div.wiki > code {
background: var(--md-sys-color-surface-container-high);
border-radius: var(--md-sys-shape-corner-extra-small);
padding-block: 0.1em;
padding-inline: 0.3em;
}
/* Syntax highlighting: core Rouge colors are fixed hex values, regrouped here onto roles */
.syntaxhl :is(.c, .ch, .cm, .cpf, .c1, .cs, .cp, .go, .w) { color: var(--md-sys-color-on-surface-variant); font-weight: normal; font-style: italic; }
.syntaxhl :is(.k, .kc, .kd, .kn, .kp, .kr, .ow) { color: var(--md-sys-color-primary); font-weight: 500; }
.syntaxhl :is(.nf, .fm, .na, .nt, .nb, .bp, .nd, .gh, .gu) { color: var(--md-sys-color-primary); font-weight: normal; }
.syntaxhl :is(.kt, .nc, .nn, .no, .m, .mb, .mf, .mh, .mi, .mo, .il) { color: var(--md-sys-color-tertiary); font-weight: normal; }
.syntaxhl :is(.s, .sa, .sb, .sc, .dl, .sd, .s2, .se, .sh, .si, .sx, .sr, .s1, .ss, .gi) { color: var(--md-sys-color-success); background-color: transparent; }
.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(.o) { color: var(--md-sys-color-on-surface); }
.syntaxhl .hll { background-color: var(--md-sys-color-secondary-container); }
/***** Activity *****/
/* One card per day (the <dl>), entries are M3 list rows. Each entry is a <dt> + <dd> pair:
dt is a grid (type icon | headline | time, project below), dd holds the clamped
description and the author. Follow-up events on the same item hang off a thread rail. */
div#activity h3 {
position: sticky;
inset-block-start: 0;
z-index: 1;
margin-block: 24px 8px;
padding-block: 8px;
padding-inline: 0;
background-color: var(--md-sys-color-surface);
color: var(--md-sys-color-on-surface);
font: var(--md-sys-typescale-title-medium);
}
div#activity h3:first-child { margin-block-start: 0; }
div#activity dl {
margin-inline-start: 0;
background-color: var(--md-sys-color-surface-container-low);
border: 1px solid var(--md-sys-color-outline-variant);
border-radius: var(--md-sys-shape-corner-medium);
overflow: hidden;
}
div#activity dt, div#activity dl dt:first-child {
display: grid;
grid-template-columns: 40px minmax(0, 1fr) auto;
grid-template-areas: "icon title time" "icon project time";
column-gap: 16px;
align-items: center;
inline-size: auto;
margin: 0;
padding-block: 12px 0;
padding-inline: 16px;
background: none;
border: 0;
border-block-start: 1px solid var(--md-sys-color-outline-variant);
border-radius: 0;
}
div#activity dl dt:first-child { border-block-start: 0; }
/* Leading element: type icon in a tonal circle, author avatar as a badge on it */
div#activity dt > svg.icon-svg {
grid-area: icon;
box-sizing: content-box;
padding: 11px;
margin: 0;
border-radius: var(--md-sys-shape-corner-full);
background-color: var(--md-sys-color-secondary-container);
color: var(--md-sys-color-on-secondary-container);
}
div#activity dt.issue > svg.icon-svg { background-color: var(--md-sys-color-primary-container); color: var(--md-sys-color-on-primary-container); }
div#activity dt.issue-closed > svg.icon-svg { background-color: var(--md-sys-color-success-container); color: var(--md-sys-color-on-success-container); }
div#activity dt:is(.wiki-page, .document, .news, .message, .attachment) > svg.icon-svg { background-color: var(--md-sys-color-tertiary-container); color: var(--md-sys-color-on-tertiary-container); }
div#activity dt > .avatar {
grid-area: icon;
align-self: end;
justify-self: end;
inline-size: 18px;
block-size: 18px;
margin: 0 -6px -4px 0;
font-size: 7px;
line-height: 18px;
box-shadow: 0 0 0 2px var(--md-sys-color-surface-container-low);
}
div#activity dt > a { grid-area: title; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; font: var(--md-sys-typescale-title-small); }
div#activity dt .time { grid-area: time; align-self: start; margin: 0; color: var(--md-sys-color-on-surface-variant); font: var(--md-sys-typescale-label-medium); }
div#activity dt.me .time { border-block-end: 0; }
div#activity span.project { grid-area: project; color: var(--md-sys-color-on-surface-variant); font: var(--md-sys-typescale-body-small); }
div#activity span.project::after { content: none; }
div#activity dd {
margin: 0;
padding-block: 2px 12px;
padding-inline: 72px 16px; /* 16 padding + 40 icon + 16 gap: under the headline */
background: none;
border: 0;
border-radius: 0;
font: var(--md-sys-typescale-body-medium);
}
div#activity dd span.description {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
margin-block: 0 2px; /* a top margin would collapse through dd */
color: var(--md-sys-color-on-surface);
font-style: normal;
}
div#activity dd span.description:empty { display: none; }
div#activity dd .author { font: var(--md-sys-typescale-label-medium); }
/* No project line (entry from the current project): the icon still spans two rows, so pull the details up */
div#activity dt:not(:has(> .project)) + dd { margin-block-start: -20px; }
/* Thread: follow-up events hang off a rail starting under the first event's icon */
div#activity dt.grouped, div#activity dd.grouped {
margin-inline-start: 36px;
box-shadow: inset 2px 0 0 var(--md-sys-color-outline-variant);
}
div#activity dt.grouped {
grid-template-columns: 32px minmax(0, 1fr) auto;
padding-inline-start: 18px;
border-block-start: 0;
}
div#activity dt.grouped > svg.icon-svg { padding: 7px; }
div#activity dd.grouped { padding-inline-start: 66px; } /* 18 + 32 + 16 */
/***** My page: each block is a card like .box; drop zones show only while dragging *****/
.mypage-box {
padding: 16px;
background-color: var(--md-sys-color-surface-container-low);
color: var(--md-sys-color-on-surface);
border: 0;
border-radius: var(--md-sys-shape-corner-medium);
box-shadow: none;
}
.mypage-box .autoscroll { margin-block-end: 0; }
.block-receiver { border-color: transparent; border-radius: var(--md-sys-shape-corner-medium); }
.dragging .block-receiver { border-color: var(--md-sys-color-outline-variant); }
/***** Search results *****/
/* The type checkboxes' legend is only a "check all" icon: it leads their row */
#search-form fieldset.box > legend { inline-size: auto; padding: 0; margin-inline-end: 8px; }
#search-form fieldset.box > legend ~ * { clear: none; }
/* A checkbox stays with its label when the row wraps */
#search-form label { display: inline-block; white-space: nowrap; }
/* Results start at the content edge; the title wraps under its project, not beside it */
#search-results { margin-inline-start: 0; }
#search-results dt { display: block; }
div#activity dt .time, #search-results dt .time { color: var(--md-sys-color-on-surface-variant); }
#search-results dd span.description { color: var(--md-sys-color-on-surface-variant); }
/***** Projects board: each root project is a filled card *****/
#projects-index ul.projects li.root {
padding: 16px;
border: 0;
border-radius: var(--md-sys-shape-corner-medium);
background-color: var(--md-sys-color-surface-container-low);
box-shadow: none;
}
#projects-index li.root ul.projects { border-inline-start: 1px solid var(--md-sys-color-outline-variant); }
/***** Documents: one filled card per category, documents are list rows inside *****/
#document-list .document-group {
margin-block-end: 16px;
padding-block: 8px;
padding-inline: 16px;
border-radius: var(--md-sys-shape-corner-medium);
background-color: var(--md-sys-color-surface-container-low);
}
#document-list .document-group h3 {
margin: 0;
padding-block: 8px;
padding-inline: 0;
background-color: transparent;
color: var(--md-sys-color-on-surface);
font: var(--md-sys-typescale-title-medium);
}
#document-list .document-item { padding-block: 12px; }
#document-list .document-item + .document-item { border-block-start: 1px solid var(--md-sys-color-outline-variant); }
#document-list .document-group h3 + .document-item { padding-block-start: 4px; }
/***** News list: each article is a filled card *****/
#news-list article.news-article {
margin-block-end: 12px;
padding: 16px;
border-radius: var(--md-sys-shape-corner-medium);
background-color: var(--md-sys-color-surface-container-low);
}
/* Hanging indent: a wrapped title and the comment count line up past the avatar */
#news-list article.news-article header h3 {
margin-block: 0 4px;
padding-inline-start: 32px;
text-indent: -32px;
font: var(--md-sys-typescale-title-medium);
}
#news-list article.news-article header h3 .avatar { margin-inline-end: 4px; vertical-align: middle; text-indent: 0; } /* + the space after it = 8px */
/* Author and text line up with the title, past the 24px avatar and its gap */
#news-list article.news-article header p.author,
#news-list article.news-article .wiki { margin-inline-start: 32px; }
#news-list article.news-article header p.author { margin-block: 0 8px; color: var(--md-sys-color-on-surface-variant); }
#news-list article.news-article .wiki > :last-child { margin-block-end: 0; }
/***** Workflow: allowed transitions use the selected state *****/
table.transitions td.enabled { background-color: var(--md-sys-color-secondary-container); color: var(--md-sys-color-on-secondary-container); }
table.fields_permissions td.readonly { background-color: var(--md-sys-color-surface-container-highest); }
table.fields_permissions td.required { background-color: var(--md-sys-color-error-container); color: var(--md-sys-color-on-error-container); }
/***** Attachments, diffs, imports, headings *****/
div.thumbnail {
overflow: hidden;
border: 1px solid var(--md-sys-color-outline-variant);
border-radius: var(--md-sys-shape-corner-small);
background: transparent;
}
div.thumbnail img { margin: 0; }
pre.text-diff {
padding: 16px;
border-color: var(--md-sys-color-outline-variant);
border-radius: var(--md-sys-shape-corner-medium);
}
h2 .avatar, h3 .avatar { margin-inline-end: 8px; }
/* "Replies (2)": the icon heading is a flex row, which drops the space before the count */
h3.comments .icon-label { margin-inline-end: 0.25em; }
#wiki_page_parent_id { min-inline-size: 240px; }
select#columns, select#criterias { margin-inline: 8px 16px; }
input#months { inline-size: 72px; }
/* Tabs start below the floated page actions, so their divider spans the page */
#content > .tabs { clear: both; }
/***** Roadmap *****/
div#roadmap .version-article {
padding: 16px;
margin-block-end: 16px;
border: 1px solid var(--md-sys-color-outline-variant);
border-radius: var(--md-sys-shape-corner-medium);
}
div#roadmap header:target { background-color: var(--md-sys-color-secondary-container); }
div#version-summary { background-color: transparent; }
/* Overview and issue table share one left edge (core indents the overview by 1.5rem) */
div#roadmap article.version-article .version-overview,
div#roadmap article.version-article .wiki,
div#roadmap article.version-article form { padding-inline-start: 0; }
div#roadmap .related-issues td.assigned_to { padding-inline: 12px 4px; }
div#roadmap .related-issues caption { padding-block-end: 8px; color: var(--md-sys-color-on-surface-variant); font: var(--md-sys-typescale-title-small); }
/* Progress bar spans the width of the issue table below it; the percentage sits at its end */
.version-overview table.progress { inline-size: calc(100% - 56px); margin-block: 6px; }
.version-overview p.percent { font: var(--md-sys-typescale-label-large); line-height: 20px; text-align: end; }
.version-overview p.progress-info { margin-block: 4px 0; color: var(--md-sys-color-on-surface-variant); font: var(--md-sys-typescale-body-small); }
/* Version page: the issue table is 70% wide next to the summary */
body.controller-versions.action-show .version-overview table.progress { inline-size: calc(70% - 56px); }
body.controller-versions.action-show .version-overview p.percent { inline-size: 70%; }
/***** Calendar *****/
ul.cal {
overflow: hidden;
border-color: var(--md-sys-color-outline-variant);
border-radius: var(--md-sys-shape-corner-medium);
box-shadow: none;
}
.cal .calbody { box-shadow: none; }
.cal .calhead, .cal .week-number { background-color: var(--md-sys-color-surface-container); color: var(--md-sys-color-on-surface-variant); }
.cal .calbody { border-color: var(--md-sys-color-outline-variant); }
.cal .calbody.this-month { background-color: var(--md-sys-color-surface); }
.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); }
/***** 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
* `header height + 8 + 20n` in the subjects column and `7 + 20n` inside #gantt_draw_area.
* Bars are centered on that 16px line; row bands run from 2px above to 2px below it.
*/
:root {
--nm-gantt-band: color-mix(in srgb, var(--md-sys-color-on-surface) 4%, transparent);
--nm-gantt-rows: repeating-linear-gradient(to bottom, var(--nm-gantt-band) 0 20px, transparent 20px 40px);
--nm-gantt-track: color-mix(in srgb, var(--md-sys-color-primary) 25%, var(--md-sys-color-surface-container-highest));
--nm-gantt-subjects-offset: 2px; /* (header height + 6) mod 20; the header has one 18px row per zoom level */
}
:root:has(#zoom[value="1"]) { --nm-gantt-subjects-offset: 4px; }
:root:has(#zoom[value="3"]) { --nm-gantt-subjects-offset: 0px; }
:root:has(#zoom[value="4"]) { --nm-gantt-subjects-offset: 18px; }
/* Redmine's geometry is fixed at 20px rows; `zoom` scales rows, bars, text and positions together.
The relation/progress SVG is drawn by JS from on-screen (already zoomed) positions, so it must
not be zoomed a second time. */
:root { --nm-gantt-zoom: 1.3; }
html table.gantt-table { zoom: var(--nm-gantt-zoom); }
html #gantt_draw_area svg { zoom: calc(1 / var(--nm-gantt-zoom)); overflow: visible; }
html #gantt_draw_area { background: var(--nm-gantt-rows) 0 5px; }
html .gantt_subjects_container { background: var(--nm-gantt-rows) 0 var(--nm-gantt-subjects-offset); }
/* Header: months / weeks / days */
html .gantt_hdr { border-color: var(--md-sys-color-outline-variant); color: var(--md-sys-color-on-surface-variant); font: var(--md-sys-typescale-label-medium); line-height: 18px; }
html .gantt_hdr[style*="background"] { background: var(--md-sys-color-surface-container) !important; }
html .gantt_hdr.nwday { background-color: color-mix(in srgb, var(--md-sys-color-on-surface) 5%, transparent); color: var(--md-sys-color-on-surface-variant); }
html .gantt_hdr a { color: var(--md-sys-color-on-surface-variant); }
/* Subjects */
html .gantt_subjects, html .gantt_selected_column_content.gantt_hdr { font: var(--md-sys-typescale-body-small); letter-spacing: var(--md-sys-typescale-body-small-tracking); }
html .gantt_subjects div { line-height: 16px; }
html .gantt_subjects div.issue-subject:hover { background-color: color-mix(in srgb, var(--md-sys-color-on-surface) var(--md-sys-state-hover-state-layer-opacity), transparent); }
html .gantt_subjects div.project-name, html .gantt_subjects div.version-name { font-weight: 500; }
/* Issue bars: 10px pills centered on the text line (core: 8px at the top of it) */
html .task_todo, html .task_done, html .task_late {
block-size: 10px;
margin-block-start: 3px;
box-sizing: border-box;
border-radius: var(--md-sys-shape-corner-full);
background-image: none;
}
html .task_todo { background-color: var(--nm-gantt-track); border-color: var(--nm-gantt-track); }
html .task_done { background-color: var(--md-sys-color-primary); border-color: var(--md-sys-color-primary); }
html .task_late { background-color: var(--md-sys-color-error); border-color: var(--md-sys-color-error); }
/* Parent issues: thin bar with triangles at both ends */
html .task_todo.parent, html .task_done.parent, html .task_late.parent { block-size: 4px; margin-block-start: 6px; border-radius: 0; }
html .task_todo.parent { background-color: var(--md-sys-color-on-surface-variant); border-color: var(--md-sys-color-on-surface-variant); }
html .task.parent.marker.starting, html .task.parent.marker.ending {
background: var(--md-sys-color-on-surface-variant);
clip-path: polygon(0 7px, 8px 7px, 4px 14px);
}
/* Project and version milestones: 2px line with a diamond marker */
html .project.task_todo, html .project.task_done, html .project.task_late,
html .version.task_todo, html .version.task_done, html .version.task_late {
block-size: 2px;
margin-block-start: 7px;
border: 0;
border-radius: 0;
}
html :is(.project, .version):is(.task_todo, .task_done, .task_late) { background-image: none; }
html :is(.project, .version).task_todo { background-color: var(--md-sys-color-outline); }
html :is(.project, .version).task_done { background-color: var(--md-sys-color-primary); }
html :is(.project, .version).task_late { background-color: var(--md-sys-color-error); }
html .version.marker, html .project.marker {
block-size: 10px;
margin-block-start: 3px;
background: var(--md-sys-color-tertiary);
clip-path: polygon(4px 0, 8px 5px, 4px 10px, 0 5px);
}
html .project.marker { background: var(--md-sys-color-primary); }
/* Labels next to bars ("New 80%") */
html .task { color: var(--md-sys-color-on-surface-variant); font: var(--md-sys-typescale-label-small); line-height: 16px; }
html .task.label.project, html .task.label.version { color: var(--md-sys-color-on-surface); font-weight: 500; }
html .version-behind-schedule a, html .issue-behind-schedule a { color: var(--md-sys-color-warning); }
html .version-overdue a, html .issue-overdue a, html .project-overdue a { color: var(--md-sys-color-error); }
/* Today line (inline style); the progress line takes its color from it */
html #today_line { border-inline-start: 2px solid var(--md-sys-color-error) !important; }
/* Relation arrows are drawn with fixed colors per relation type */
html #gantt_draw_area path[stroke="#228be6"] { stroke: var(--md-sys-color-primary); }
html #gantt_draw_area path[fill="#228be6"] { fill: var(--md-sys-color-primary); }
html #gantt_draw_area path[stroke="#fa5252"] { stroke: var(--md-sys-color-error); }
html #gantt_draw_area path[fill="#fa5252"] { fill: var(--md-sys-color-error); }
/***** Issue context menu (context_menu.css loads after the theme) *****/
html #context-menu { font: var(--md-sys-typescale-label-large); }
html #context-menu ul {
background: var(--md-sys-color-surface-container);
border: 0;
border-radius: var(--md-sys-shape-corner-extra-small);
box-shadow: var(--md-sys-elevation-level2);
padding-block: 8px;
}
html #context-menu li { border: 0; border-radius: 0; }
html #context-menu li:hover { border: 0; border-radius: 0; background-color: color-mix(in srgb, var(--md-sys-color-on-surface) var(--md-sys-state-hover-state-layer-opacity), transparent); }
html #context-menu a { color: var(--md-sys-color-on-surface); padding-block: 8px; }
html #context-menu a:hover { color: var(--md-sys-color-on-surface); text-decoration: none; }
html #context-menu a.disabled, html #context-menu a.disabled:hover { color: color-mix(in srgb, var(--md-sys-color-on-surface) var(--md-sys-state-disabled-content-opacity), transparent); }
html #context-menu a.disabled svg.icon-svg { color: inherit; }
.cal .calbody div.issue {
margin-block: 4px;
padding-block: 4px;
padding-inline: 8px;
background-color: var(--md-sys-color-surface-container);
border-radius: var(--md-sys-shape-corner-small);
font: var(--md-sys-typescale-body-small);
}
+35
View File
@@ -0,0 +1,35 @@
/*
* Generated by tools/palettes/build_palettes.mjs; do not edit by hand.
* M3 color roles (TonalSpot, contrast 0) per palette, light and dark.
* <html data-palette="..."> picks the palette, <html data-mode="light|dark"> forces a mode;
* without data-mode the OS setting (prefers-color-scheme) decides.
*/
:root { color-scheme: light; }
:root[data-mode="dark"] { color-scheme: dark; }
@media (prefers-color-scheme: dark) { :root:not([data-mode="light"]) { color-scheme: dark; } }
/* purple (seed #6750A4) */
: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; }
@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; } }
/* 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"][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; } }
/* teal (seed #006A6A) */
: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; }
: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; }
@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; } }
/* 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"][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; } }
/* 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"][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; } }
+159
View File
@@ -0,0 +1,159 @@
/*
* Data tables (M3 has no table spec): surface rows, outline-variant dividers,
* title-small headers, hover state layer, secondary-container selection.
* Compact density: ~40px rows.
*/
table.list, .table-list {
font: var(--md-sys-typescale-body-medium);
letter-spacing: var(--md-sys-typescale-body-medium-tracking);
font-variant-numeric: tabular-nums;
border: 1px solid var(--md-sys-color-outline-variant);
border-radius: var(--md-sys-shape-corner-medium);
border-collapse: separate;
border-spacing: 0;
margin-block-end: 8px;
}
/* A table that scrolls sideways: the wrapper is the outlined card, so the card edge and
corners stay visible while the table scrolls inside it */
.autoscroll:has(> table.list) {
clear: both; /* scrolling makes it narrow beside a float instead of going below it */
padding: 0;
margin-block-end: 8px;
border: 1px solid var(--md-sys-color-outline-variant);
border-radius: var(--md-sys-shape-corner-medium);
}
.autoscroll > table.list { margin: 0; border: 0; border-radius: 0; }
table.list th, .table-list-header {
background-color: var(--md-sys-color-surface-container);
color: var(--md-sys-color-on-surface-variant);
font: var(--md-sys-typescale-title-small);
letter-spacing: var(--md-sys-typescale-title-small-tracking);
padding-block: 10px;
padding-inline: 8px;
border-block-end: 1px solid var(--md-sys-color-outline-variant);
}
table.list th a, table.list th a:link, table.list th a:visited { color: var(--md-sys-color-on-surface-variant); }
table.list td {
block-size: 40px;
padding-block: 4px;
padding-inline: 8px;
border-block-start: 1px solid var(--md-sys-color-outline-variant);
background-color: transparent;
}
table.list tbody tr:first-child td, table.list tbody tr.group + tr td { border-block-start: 0; }
/* No striping: M3 separates rows with dividers */
table.list:not(.odd-even) tbody tr:nth-child(odd), table.list:not(.odd-even) tbody tr:nth-child(even),
.odd, .even, .box table.list:not(.odd-even) tbody tr:nth-child(even), .box .even {
background-color: transparent;
}
table.list:not(.odd-even) tbody tr:nth-child(odd):hover, table.list:not(.odd-even) tbody tr:nth-child(even):hover,
.odd:hover, .even:hover {
background-color: color-mix(in srgb, var(--md-sys-color-on-surface) var(--md-sys-state-hover-state-layer-opacity), transparent);
}
/* Status names in the workflow header are td cells: same band as the th row above */
table.workflows thead td {
background-color: var(--md-sys-color-surface-container);
color: var(--md-sys-color-on-surface-variant);
font: var(--md-sys-typescale-title-small);
letter-spacing: var(--md-sys-typescale-title-small-tracking);
border-block-end: 1px solid var(--md-sys-color-outline-variant);
}
/* Role names in permission group rows sit on the group name's line */
table.permissions tr.group td { vertical-align: middle; }
/* Field permissions: the "»" copy link stays next to its select */
table.fields_permissions td { white-space: nowrap; }
/* An empty table: no divider under the header, the table border closes it */
table.list:not(:has(tbody tr)) th { border-block-end: 0; }
/* Child projects: chevron in the theme's colour, one file per mode */
tr.idnt :is(td.subject, td.name) { background-image: url(../images/chevron-right-idnt.svg); }
[dir="rtl"] tr.idnt :is(td.subject, td.name) { background-image: url(../images/chevron-left-idnt.svg); }
:root[data-mode="dark"] tr.idnt :is(td.subject, td.name) { background-image: url(../images/chevron-right-idnt-dark.svg); }
:root[data-mode="dark"][dir="rtl"] tr.idnt :is(td.subject, td.name) { background-image: url(../images/chevron-left-idnt-dark.svg); }
@media (prefers-color-scheme: dark) {
:root:not([data-mode="light"]) tr.idnt :is(td.subject, td.name) { background-image: url(../images/chevron-right-idnt-dark.svg); }
:root:not([data-mode="light"])[dir="rtl"] tr.idnt :is(td.subject, td.name) { background-image: url(../images/chevron-left-idnt-dark.svg); }
}
/* Repository file, annotate and diff views: outlined like table.list, line numbers
separated by one divider instead of a grid (scm.css loads after the theme, hence html) */
html table.filecontent {
inline-size: 100%;
overflow: hidden;
border: 1px solid var(--md-sys-color-outline-variant);
border-collapse: separate;
border-spacing: 0;
border-radius: var(--md-sys-shape-corner-medium);
background-color: var(--md-sys-color-surface-container-lowest);
}
html table.filecontent th, html table.filecontent th.line-num {
border: 0;
border-inline-end: 1px solid var(--md-sys-color-outline-variant);
background-color: var(--md-sys-color-surface-container);
}
html table.filecontent th.filename {
padding-block: 10px;
padding-inline: 12px;
border-inline-end: 0;
border-block-end: 1px solid var(--md-sys-color-outline-variant);
color: var(--md-sys-color-on-surface-variant);
font: var(--md-sys-typescale-title-small);
}
/* CSV import preview */
.sample-data {
margin: 0;
overflow: hidden;
border: 1px solid var(--md-sys-color-outline-variant);
border-collapse: separate;
border-spacing: 0;
border-radius: var(--md-sys-shape-corner-medium);
background-color: transparent;
}
.sample-data td { padding-block: 8px; padding-inline: 12px; border: 0; border-block-start: 1px solid var(--md-sys-color-outline-variant); }
.sample-data tr:first-child td { border-block-start: 0; background-color: var(--md-sys-color-surface-container); }
tr.group td {
padding-block: 12px 8px;
padding-inline: 8px;
border-block-end: 1px solid var(--md-sys-color-outline-variant);
background-color: var(--md-sys-color-surface-container-low);
color: var(--md-sys-color-on-surface);
}
tr.group span.name { font-weight: 500; }
tr.group span.totals, tr.group a.toggle-all { color: var(--md-sys-color-on-surface-variant); }
tr.group span.totals .value { color: var(--md-sys-color-on-surface); font-weight: 500; }
table.list tbody tr.group:hover { background-color: transparent; }
/* Selected rows (context menu); core uses !important */
html .context-menu-selection, html .context-menu-selection:hover {
background-color: var(--md-sys-color-secondary-container) !important;
color: var(--md-sys-color-on-secondary-container) !important;
}
html .context-menu-selection a, html .context-menu-selection a:hover { color: var(--md-sys-color-on-secondary-container) !important; }
html .context-menu-selection svg.icon-svg { stroke: var(--md-sys-color-on-secondary-container) !important; }
table.list tr.overdue:not(.context-menu-selection) td.due_date { color: var(--md-sys-color-error); }
a.issue.closed, a.issue.closed:link, a.issue.closed:visited { color: var(--md-sys-color-on-surface-variant); }
.query-totals { color: var(--md-sys-color-on-surface-variant); }
.query-totals .value { color: var(--md-sys-color-on-surface); font-weight: 500; }
#query_form_content { font-size: inherit; }
#query_form_with_buttons > p.contextual { font: var(--md-sys-typescale-label-large); }
/* Query filters: checkbox + label sit level with the 40px operator/value fields. The field cell is
40px high and centers its content; the row stays top-aligned so an expanded multi-select grows
downwards without dragging the label to its middle. */
#filters-table .filter { align-items: flex-start; margin-block: 4px 0; }
#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 .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; }
/* Core fixes the operator at 120px, too narrow for "has never been" with M3 field padding */
#filters-table .filter .operator select { inline-size: 180px; }
/* All value fields share one width so the filter rows form a clean second column.
"between" filters show two fields (both spans visible): each takes half. */
#filters-table .filter .values select.value,
#filters-table .filter .values input.value:not([size="3"]) { inline-size: 260px; }
#filters-table .filter .values:has(> span:first-child + span:not([style*="none"])) input.value { inline-size: 126px; padding-inline: 8px; }
+74
View File
@@ -0,0 +1,74 @@
/*
* Material Design 3 system tokens (type, shape, state, motion, elevation).
* Color roles live in palettes.css, generated by tools/palettes/build_palettes.mjs.
*/
:root {
/* Typeface */
--md-ref-typeface-plain: Roboto, system-ui, sans-serif;
/* Type scale, as `font` shorthand values: weight size/line-height family */
--md-sys-typescale-display-large: 400 3.5625rem/4rem var(--md-ref-typeface-plain);
--md-sys-typescale-display-medium: 400 2.8125rem/3.25rem var(--md-ref-typeface-plain);
--md-sys-typescale-display-small: 400 2.25rem/2.75rem var(--md-ref-typeface-plain);
--md-sys-typescale-headline-large: 400 2rem/2.5rem var(--md-ref-typeface-plain);
--md-sys-typescale-headline-medium: 400 1.75rem/2.25rem var(--md-ref-typeface-plain);
--md-sys-typescale-headline-small: 400 1.5rem/2rem var(--md-ref-typeface-plain);
--md-sys-typescale-title-large: 400 1.375rem/1.75rem var(--md-ref-typeface-plain);
--md-sys-typescale-title-medium: 500 1rem/1.5rem var(--md-ref-typeface-plain);
--md-sys-typescale-title-small: 500 0.875rem/1.25rem var(--md-ref-typeface-plain);
--md-sys-typescale-body-large: 400 1rem/1.5rem var(--md-ref-typeface-plain);
--md-sys-typescale-body-medium: 400 0.875rem/1.25rem var(--md-ref-typeface-plain);
--md-sys-typescale-body-small: 400 0.75rem/1rem var(--md-ref-typeface-plain);
--md-sys-typescale-label-large: 500 0.875rem/1.25rem var(--md-ref-typeface-plain);
--md-sys-typescale-label-medium: 500 0.75rem/1rem var(--md-ref-typeface-plain);
--md-sys-typescale-label-small: 500 0.6875rem/1rem var(--md-ref-typeface-plain);
/* Tracking (letter-spacing) per type role */
--md-sys-typescale-display-large-tracking: -0.25px;
--md-sys-typescale-title-medium-tracking: 0.15px;
--md-sys-typescale-title-small-tracking: 0.1px;
--md-sys-typescale-body-large-tracking: 0.5px;
--md-sys-typescale-body-medium-tracking: 0.25px;
--md-sys-typescale-body-small-tracking: 0.4px;
--md-sys-typescale-label-large-tracking: 0.1px;
--md-sys-typescale-label-medium-tracking: 0.5px;
--md-sys-typescale-label-small-tracking: 0.5px;
/* Shape */
--md-sys-shape-corner-none: 0;
--md-sys-shape-corner-extra-small: 4px;
--md-sys-shape-corner-small: 8px;
--md-sys-shape-corner-medium: 12px;
--md-sys-shape-corner-large: 16px;
--md-sys-shape-corner-extra-large: 28px;
--md-sys-shape-corner-full: 9999px;
/* State layer opacities */
--md-sys-state-hover-state-layer-opacity: 8%;
--md-sys-state-focus-state-layer-opacity: 10%;
--md-sys-state-pressed-state-layer-opacity: 10%;
--md-sys-state-dragged-state-layer-opacity: 16%;
--md-sys-state-disabled-content-opacity: 38%;
--md-sys-state-disabled-container-opacity: 12%;
/* Motion */
--md-sys-motion-duration-short2: 100ms;
--md-sys-motion-duration-short4: 200ms;
--md-sys-motion-duration-medium2: 300ms;
--md-sys-motion-duration-medium4: 400ms;
--md-sys-motion-duration-long2: 500ms;
--md-sys-motion-easing-standard: cubic-bezier(0.2, 0, 0, 1);
--md-sys-motion-easing-standard-decelerate: cubic-bezier(0, 0, 0, 1);
--md-sys-motion-easing-standard-accelerate: cubic-bezier(0.3, 0, 1, 1);
--md-sys-motion-easing-emphasized-decelerate: cubic-bezier(0.05, 0.7, 0.1, 1);
--md-sys-motion-easing-emphasized-accelerate: cubic-bezier(0.3, 0, 0.8, 0.15);
/* Elevation (shadow part; M3 prefers surface-container roles for depth) */
--md-sys-elevation-level0: none;
--md-sys-elevation-level1: 0 1px 2px rgb(0 0 0 / 30%), 0 1px 3px 1px rgb(0 0 0 / 15%);
--md-sys-elevation-level2: 0 1px 2px rgb(0 0 0 / 30%), 0 2px 6px 2px rgb(0 0 0 / 15%);
--md-sys-elevation-level3: 0 1px 3px rgb(0 0 0 / 30%), 0 4px 8px 3px rgb(0 0 0 / 15%);
--md-sys-elevation-level4: 0 2px 3px rgb(0 0 0 / 30%), 0 6px 10px 4px rgb(0 0 0 / 15%);
--md-sys-elevation-level5: 0 4px 4px rgb(0 0 0 / 30%), 0 8px 12px 6px rgb(0 0 0 / 15%);
}
+40
View File
@@ -0,0 +1,40 @@
/*
* One type scale for the whole app. Core Redmine sets ~60 relative sizes (0.8125rem, 0.9em, 93%, …)
* that compound when nested, e.g. the sidebar at 13px and its "Add" link at 11.7px.
* Here they are mapped onto the M3 scale: 14px (body-medium) for anything readable,
* 12px (body-small) for secondary meta text, headings on the heading scale.
* e2e/check-font-sizes.mjs reports anything that falls off the scale again.
*/
/* 14px: content, lists, actions, sidebars, forms */
#sidebar, .contextual, .contextual input, .contextual select, .buttons, #query_form_content,
#query_form_with_buttons > p.contextual, .query-totals, span.search_for_watchers, span.add_attachment,
table.issues td.block_column, tr.message td.last_message, table.boards td.last-message,
.tabular.settings p, #errorExplanation ul, .conflict-details, div.wiki ul.toc a,
#document-list .document-item .wiki.description, table.permissions td.role, #workflow_form table select,
tr.group span.totals, tr.group a.toggle-all, ul.properties, #sidebar h3 + .contextual, #search-results dd {
font-size: 0.875rem;
}
/* Parts of a number (hours "8:00") keep the number's size */
.hours-dec, table.time-entries td.hours .hours-dec, table#time-report .hours-dec, .total-hours span.hours-int {
font-size: inherit;
}
/* 12px: secondary meta text (who/when, stats, breadcrumbs, footers of blocks) */
p.breadcrumb, p.subtitle, p.footnote, div.issue p.author, div.issue div.subject p,
#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,
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 {
font-size: 0.75rem;
}
div.issue span.private, div.journal span.private { font-size: 0.6875rem; }
/* Headings */
.wiki h1, #content .wiki h1 { font: var(--md-sys-typescale-headline-small); }
.wiki h2, #content .wiki h2 { font: var(--md-sys-typescale-title-large); }
.wiki h3, #content .wiki h3, div#roadmap .wiki h1, div#roadmap .wiki h2 { font: var(--md-sys-typescale-title-medium); letter-spacing: var(--md-sys-typescale-title-medium-tracking); }
.wiki h4, #content .wiki h4 { font: var(--md-sys-typescale-title-small); letter-spacing: var(--md-sys-typescale-title-small-tracking); }
#document-list .document-item h4.title { font: var(--md-sys-typescale-title-medium); }
.total-hours { font: var(--md-sys-typescale-title-medium); }
.cal .calbody p.day-num { font: var(--md-sys-typescale-label-large); }
+64
View File
@@ -0,0 +1,64 @@
# Builds tonalmine/images/icons.svg from tools/icon_map.yml.
# Usage (from repo root): ruby tools/build_icons.rb
# Ruby stdlib only. Downloads are cached in tools/.cache/.
require 'yaml'
require 'net/http'
require 'uri'
require 'fileutils'
CORE_ICONS = 'https://raw.githubusercontent.com/redmine/redmine/7.0-stable/config/icon_source.yml'
SYMBOLS = 'https://raw.githubusercontent.com/google/material-design-icons/master/symbols/web'
ROOT = File.expand_path('..', __dir__)
CACHE = File.join(ROOT, 'tools/.cache')
OUT = File.join(ROOT, 'tonalmine/images/icons.svg')
def fetch(url)
res = Net::HTTP.get_response(URI(url))
res.is_a?(Net::HTTPSuccess) ? res.body : nil
end
def fail_with(message, items)
warn "#{message}:\n #{items.join("\n ")}"
exit 1
end
# 1. Map must cover exactly the core icon names.
core_yml = fetch(CORE_ICONS) or fail_with('Could not fetch', [CORE_ICONS])
core = YAML.safe_load(core_yml).map { |e| e['name'] }.uniq
map = YAML.load_file(File.join(ROOT, 'tools/icon_map.yml'))
names = map.map { |e| e['name'] }
dupes = names.select { |n| names.count(n) > 1 }.uniq
fail_with('Duplicate names in icon_map.yml', dupes) if dupes.any?
missing = core - names
extra = names - core
fail_with('Missing from icon_map.yml', missing) if missing.any?
fail_with('Not in Redmine icon_source.yml (extra)', extra) if extra.any?
# 2. Download (or read cached) SVGs.
FileUtils.mkdir_p(CACHE)
not_found = []
symbols = map.sort_by { |e| e['name'] }.map do |e|
file = "#{e['symbol']}#{e['fill'] ? '_fill1' : ''}_24px.svg"
cached = File.join(CACHE, file)
unless File.exist?(cached)
svg = fetch("#{SYMBOLS}/#{e['symbol']}/materialsymbolsoutlined/#{file}")
next not_found << "#{e['name']} -> #{file}" unless svg
File.write(cached, svg)
end
paths = File.read(cached).scan(/<path\b[^>]*\bd="([^"]+)"/).flatten
fail_with('No <path> found in', [cached]) if paths.empty?
body = paths.map { |d| %(<path d="#{d}" fill="currentColor" stroke="none"/>) }.join
%(<symbol id="icon--#{e['name']}" viewBox="0 -960 960 960">#{body}</symbol>)
end
fail_with('Material Symbol not found', not_found) if not_found.any?
# 3. Write the sprite.
File.write(OUT, <<~SVG)
<svg xmlns="http://www.w3.org/2000/svg">
<!-- Generated by tools/build_icons.rb from tools/icon_map.yml. Do not edit by hand.
Icons: Material Symbols Outlined by Google (github.com/google/material-design-icons),
Apache License 2.0, see LICENSE-material-symbols.txt. -->
#{symbols.join("\n")}
</svg>
SVG
puts "Wrote #{symbols.size} icons to #{OUT.sub("#{ROOT}/", '')}"
+242
View File
@@ -0,0 +1,242 @@
# Redmine 7.0 icon name -> Material Symbols (Outlined, weight 400) name.
# Order follows Redmine's config/icon_source.yml. `fill: true` uses the filled variant.
# Rebuild the sprite with: ruby tools/build_icons.rb
- name: edit
symbol: edit
- name: add
symbol: add_circle
- name: copy
symbol: content_copy
- name: del
symbol: delete
- name: save
symbol: save
- name: download
symbol: download
- name: attachment
symbol: attach_file
- name: time-add
symbol: more_time
- name: time
symbol: schedule
- name: fav
symbol: star
- name: copy-link
symbol: link # note: core uses a clipboard; "link" reads better for "Copy link"
- name: 3-bullets
symbol: more_horiz
- name: history
symbol: history
- name: folder
symbol: folder
- name: folder-open
symbol: folder_open
- name: lock
symbol: lock
- name: unlock
symbol: lock_open
- name: link-break
symbol: link_off
- name: zoom-in
symbol: zoom_in
- name: zoom-out
symbol: zoom_out
- name: settings
symbol: settings
- name: news
symbol: newspaper
- name: user
symbol: person
- name: group
symbol: group
- name: bookmarked
symbol: bookmark
- name: bookmark-delete
symbol: bookmark_remove
- name: bookmark-add
symbol: bookmark_add
- name: import
symbol: database_upload
- name: summary
symbol: bolt # note: mirrors core's Tabler "bolt"
- name: angle-down
symbol: expand_more
- name: angle-right
symbol: chevron_right
- name: angle-up
symbol: expand_less
- name: angle-left
symbol: chevron_left
- name: email
symbol: mail
- name: email-disabled
symbol: mail_off
- name: stats
symbol: bar_chart
- name: reorder
symbol: reorder # note: Tabler "menu-order" has up/down arrows; Material is plain lines
- name: close
symbol: close # note: plain X, no square frame
- name: checked
symbol: check
- name: reload
symbol: refresh
- name: link
symbol: link
- name: plugins
symbol: extension
- name: roles
symbol: admin_panel_settings # note: Tabler "shield-cog"
- name: list
symbol: list
- name: workflows
symbol: account_tree # note: Tabler "jump-rope" has no Material equivalent
- name: server-authentication
symbol: dns
- name: table-multiple
symbol: refresh # note: mirrors core, which also draws "refresh" here
- name: projects
symbol: inventory_2 # note: Tabler "packages"
- name: project
symbol: inventory_2 # note: Tabler "packages"
- name: package
symbol: package_2
- name: custom-fields
symbol: dynamic_form # note: Tabler "input-check"
- name: help
symbol: info
- name: changeset
symbol: code
- name: clear-query
symbol: filter_alt_off # note: by meaning; core uses a square X
- name: warning
symbol: warning
- name: comments
symbol: forum
- name: comment
symbol: comment
- name: message
symbol: chat_bubble
- name: reply
symbol: reply # note: by meaning; core uses Tabler "messages"
- name: arrow-right
symbol: arrow_forward
- name: wiki-page
symbol: article # note: Tabler "notebook"
- name: bullet-end
symbol: arrow_circle_left
- name: bullet-go
symbol: arrow_circle_right
- name: bullet-go-end
symbol: diamond # note: Tabler "diamonds"
- name: move
symbol: move_item # note: Tabler "arrow-forward-up"
- name: cancel
symbol: undo
- name: document
symbol: description
- name: issue
symbol: sticky_note_2 # note: Tabler "note"
- name: issue-closed
symbol: check_box
- name: issue-edit
symbol: edit_square
- name: issue-note
symbol: add_comment
- name: file
symbol: draft
- name: text-plain
symbol: description
- name: text-x-c
symbol: code_blocks # note: generic code file
- name: text-x-csharp
symbol: code_blocks # note: generic code file
- name: text-x-java
symbol: code_blocks # note: generic code file
- name: text-x-php
symbol: php
- name: text-x-ruby
symbol: code_blocks # note: generic code file
- name: text-xml
symbol: code # note: generic markup
- name: text-css
symbol: css
- name: text-html
symbol: html
- name: application-javascript
symbol: javascript
- name: application-pdf
symbol: picture_as_pdf
- name: application-zip
symbol: folder_zip
- name: application-gzip
symbol: folder_zip
- name: chevrons-right
symbol: keyboard_double_arrow_right
- name: chevrons-left
symbol: keyboard_double_arrow_left
- name: key
symbol: key
- name: search
symbol: search
- name: toggle-plus
symbol: add_box
- name: toggle-minus
symbol: indeterminate_check_box
- name: circle-minus
symbol: do_not_disturb_on
- name: circle-dot-filled
symbol: radio_button_checked
fill: true
- name: watch
symbol: visibility
- name: unwatch
symbol: visibility_off
- name: copy-pre-content
symbol: content_paste
- name: thumb-up
symbol: thumb_up
- name: thumb-up-filled
symbol: thumb_up
fill: true
- name: alert-circle
symbol: error
- name: bulb
symbol: lightbulb
- name: message-report
symbol: feedback
- name: quote-filled
symbol: format_quote
fill: true
- name: loader
symbol: progress_activity
- name: hourglass
symbol: hourglass
- name: webhook
symbol: webhook
- name: apps
symbol: apps
- name: shield-check
symbol: verified_user
- name: arrow-narrow-left
symbol: arrow_left_alt
- name: arrow-narrow-right
symbol: arrow_right_alt
- name: photo
symbol: image
- name: movie
symbol: movie
- name: file-music
symbol: audio_file
- name: file-type-docx
symbol: docs # note: generic document with lines
- name: file-type-xls
symbol: table # note: generic spreadsheet
- name: file-type-ppt
symbol: slideshow # note: generic presentation
- name: maximize
symbol: fullscreen
- name: link-add
symbol: add_link
- name: unknown
symbol: help
+1
View File
@@ -0,0 +1 @@
.build/
+73
View File
@@ -0,0 +1,73 @@
// 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
// (bundled with esbuild because material-color-utilities 0.4.0 has extensionless ESM imports).
import { writeFileSync, readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { Hct, SchemeTonalSpot, MaterialDynamicColors as C, hexFromArgb, argbFromHex, customColor } from '@material/material-color-utilities';
// The first palette is the default. Ids must match PALETTES in tonalmine/javascripts/theme.js.
const PALETTES = [
['purple', '#6750A4'],
['blue', '#0061A4'],
['teal', '#006A6A'],
['green', '#386A20'],
['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());
const hex = (argb) => hexFromArgb(argb);
function roles(seedHex, dark) {
const seed = argbFromHex(seedHex);
const scheme = new SchemeTonalSpot(Hct.fromInt(seed), dark, 0);
const lines = 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(' ');
}
// 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.
let css = `/*
* Generated by tools/palettes/build_palettes.mjs; do not edit by hand.
* M3 color roles (TonalSpot, contrast 0) per palette, light and dark.
* <html data-palette="..."> picks the palette, <html data-mode="light|dark"> forces a mode;
* without data-mode the OS setting (prefers-color-scheme) decides.
*/
:root { color-scheme: light; }
:root[data-mode="dark"] { color-scheme: dark; }
@media (prefers-color-scheme: dark) { :root:not([data-mode="light"]) { color-scheme: dark; } }
`;
PALETTES.forEach(([id, seed], i) => {
const sel = i === 0 ? ':root' : `:root[data-palette="${id}"]`;
css += `
/* ${id} (seed ${seed}) */
${sel} { ${roles(seed, false)} }
${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/.
const theme = resolve(process.cwd(), '../../tonalmine');
const out = `${theme}/stylesheets/palettes.css`;
writeFileSync(out, css);
// Keep theme.js in sync: every palette id must appear in its PALETTES list.
const js = readFileSync(`${theme}/javascripts/theme.js`, 'utf8');
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); }
console.log(`Wrote ${PALETTES.length} palettes to ${out}`);
+505
View File
@@ -0,0 +1,505 @@
{
"name": "tonalmine-palettes",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "tonalmine-palettes",
"devDependencies": {
"@material/material-color-utilities": "0.4.0",
"esbuild": "^0.25.0"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
"integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
"integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
"integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
"integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
"integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
"integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
"integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
"integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
"integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
"integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
"integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
"integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
"integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
"integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
"integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
"integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
"integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
"integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
"integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
"integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
"integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openharmony-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
"integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openharmony"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
"integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
"integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
"integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
"integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@material/material-color-utilities": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@material/material-color-utilities/-/material-color-utilities-0.4.0.tgz",
"integrity": "sha512-dlq6VExJReb8dhjj3a/yTigr3ncNwoFmL5Iy2ENtbDX03EmNeOEdZ+vsaGrj7RTuO+mB7L58II4LCsl4NpM8uw==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/esbuild": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
"integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.25.12",
"@esbuild/android-arm": "0.25.12",
"@esbuild/android-arm64": "0.25.12",
"@esbuild/android-x64": "0.25.12",
"@esbuild/darwin-arm64": "0.25.12",
"@esbuild/darwin-x64": "0.25.12",
"@esbuild/freebsd-arm64": "0.25.12",
"@esbuild/freebsd-x64": "0.25.12",
"@esbuild/linux-arm": "0.25.12",
"@esbuild/linux-arm64": "0.25.12",
"@esbuild/linux-ia32": "0.25.12",
"@esbuild/linux-loong64": "0.25.12",
"@esbuild/linux-mips64el": "0.25.12",
"@esbuild/linux-ppc64": "0.25.12",
"@esbuild/linux-riscv64": "0.25.12",
"@esbuild/linux-s390x": "0.25.12",
"@esbuild/linux-x64": "0.25.12",
"@esbuild/netbsd-arm64": "0.25.12",
"@esbuild/netbsd-x64": "0.25.12",
"@esbuild/openbsd-arm64": "0.25.12",
"@esbuild/openbsd-x64": "0.25.12",
"@esbuild/openharmony-arm64": "0.25.12",
"@esbuild/sunos-x64": "0.25.12",
"@esbuild/win32-arm64": "0.25.12",
"@esbuild/win32-ia32": "0.25.12",
"@esbuild/win32-x64": "0.25.12"
}
}
}
}
+12
View File
@@ -0,0 +1,12 @@
{
"name": "tonalmine-palettes",
"private": true,
"type": "module",
"scripts": {
"build": "esbuild build_palettes.mjs --bundle --platform=node --format=esm --outfile=.build/palettes.mjs --log-level=warning && node .build/palettes.mjs"
},
"devDependencies": {
"@material/material-color-utilities": "0.4.0",
"esbuild": "^0.25.0"
}
}