`, ``, ` ` inside `` (`size="xs"` for a dense one); cell utilities (`text-end`, `whitespace-nowrap`) always win. Scrolling is yours: wrap it in ``. A row that opens something is `data-list-row` with one `data-list-open` control; a selected row is `aria-selected="true"`.
diff --git a/resources/js/theme.js b/resources/js/theme.js
index e234f378..f81dadb8 100644
--- a/resources/js/theme.js
+++ b/resources/js/theme.js
@@ -7,6 +7,10 @@
*
* `value` is an accessor, so binding a control with `x-model="$store.theme.value"` goes
* through the same write as `set()` and `toggle()`: the attributes, then localStorage.
+ *
+ * `scheme` is the colour profile on screen (, which the server chose), and
+ * `previewScheme(name)` shows another one on this page without storing anything — the
+ * application saves a choice itself, and the next full load draws what it saved.
*/
document.addEventListener('alpine:init', () => {
const root = document.documentElement
@@ -18,6 +22,7 @@ document.addEventListener('alpine:init', () => {
window.Alpine.store('theme', {
choice: choices.includes(root.dataset.themeChoice) ? root.dataset.themeChoice : 'system',
resolved: root.dataset.theme === 'dark' ? 'dark' : 'light',
+ scheme: root.dataset.scheme || null,
get value() {
return this.choice
@@ -48,6 +53,15 @@ document.addEventListener('alpine:init', () => {
toggle() {
this.set(this.resolved === 'dark' ? 'light' : 'dark')
},
+
+ previewScheme(name) {
+ if (typeof name !== 'string' || !/^[a-z0-9-]+$/.test(name)) {
+ return
+ }
+
+ this.scheme = name
+ root.dataset.scheme = name
+ },
})
// The head script repaints on an OS change while the choice is `system`; this keeps the
diff --git a/resources/views/components/scheme-picker.blade.php b/resources/views/components/scheme-picker.blade.php
new file mode 100644
index 00000000..09ad1c0b
--- /dev/null
+++ b/resources/views/components/scheme-picker.blade.php
@@ -0,0 +1,76 @@
+{{-- A choice of colour profile: one swatch per profile `material:scheme` generated from
+ `livewire-material.profiles`, each its name and its primary, secondary and tertiary colour.
+
+
+
+ Native radios under the swatches, so `wire:model` and `x-model` bind as on any input and the
+ arrow keys move the choice. Choosing one shows it on the page at once
+ (`$store.theme.previewScheme`); storing it — and telling `Scheme::resolveProfileUsing()` — is
+ the application's. The dots are the only colours not drawn from tokens: they show other
+ profiles than the page's, so they are custom properties set inline from the scheme file's
+ checked hexes, light or dark with the page. Without profiles it renders nothing.
+
+ Props: `label`, `hint`, `name` (needed with `x-model`), `profiles` (default: every generated
+ profile). A validation message for the bound property replaces the hint. --}}
+
+@props([
+ 'label' => null,
+ 'hint' => null,
+ 'name' => null,
+ 'profiles' => null,
+])
+
+@php
+ $profiles ??= \NoNameWeb\LivewireMaterial\Support\Scheme::profiles();
+ $model = $attributes->whereStartsWith('wire:model')->first();
+ $name ??= $model ?: 'scheme';
+ $errorKey = $model ?: (filled($attributes->get('name')) ? (string) $attributes->get('name') : null);
+ $messages = $errorKey !== null && isset($errors) ? \Illuminate\Support\Arr::flatten($errors->get($errorKey)) : [];
+@endphp
+
+@if ($profiles !== [])
+
only(['class', 'wire:key'])->class('min-w-0') }}>
+ @if (filled($label))
+ {{ $label }}
+ @endif
+
+
+ @foreach ($profiles as $profile => $scheme)
+
+ whereStartsWith(['wire:model', 'x-model']) }}
+ type="radio"
+ name="{{ $name }}"
+ value="{{ $profile }}"
+ x-on:change="$store.theme.previewScheme($event.target.value)"
+ class="peer sr-only"
+ />
+
+
+ @foreach (['primary', 'secondary', 'tertiary'] as $role)
+
+ @endforeach
+
+
+ {{ __($scheme['label']) }}
+
+
+
+ @endforeach
+
+
+ @if ($messages !== [])
+ @foreach ($messages as $message)
+ {{ $message }}
+ @endforeach
+ @elseif (filled($hint))
+ {{ $hint }}
+ @endif
+
+@endif
diff --git a/resources/views/components/theme-script.blade.php b/resources/views/components/theme-script.blade.php
index 2df86b1d..f8d73a84 100644
--- a/resources/views/components/theme-script.blade.php
+++ b/resources/views/components/theme-script.blade.php
@@ -11,6 +11,10 @@
JSON-encoded (maryUI's `"dark"`) — is adopted once and removed. Nothing is written for a
visitor who never chose, so changing `theme.default` later reaches them too.
+ With colour profiles (`livewire-material.profiles`, generated by `material:scheme`), the
+ active one — `Scheme::profile()`, which asks the application's resolver — is written to
+ , which the generated stylesheet keys each profile on.
+
The rail rides along for the theme's reason: is `expanded` or `collapsed`
(`livewire-material.rail.storage_key`, falling back to `rail.default`), and a collapsible
rail's width is CSS keyed on it (the `rail-collapsed:` variant). Set any later, a collapsed
@@ -27,6 +31,7 @@
$rail = config('livewire-material.rail');
$settings = [
+ 'scheme' => \NoNameWeb\LivewireMaterial\Support\Scheme::profile(),
'default' => in_array($theme['default'] ?? null, ['light', 'dark', 'system'], true) ? $theme['default'] : 'system',
'key' => $theme['storage_key'] ?? 'material-theme',
'legacy' => array_values($theme['legacy_keys'] ?? []),
@@ -78,6 +83,10 @@
root.setAttribute('data-theme', current === 'system' ? (media.matches ? 'dark' : 'light') : current);
};
+ if (settings.scheme) {
+ root.setAttribute('data-scheme', settings.scheme);
+ }
+
root.setAttribute('data-theme-key', settings.key);
root.setAttribute('data-theme-choice', choice);
root.setAttribute('data-rail-key', settings.rail.key);
@@ -87,7 +96,7 @@
media.addEventListener('change', apply);
document.addEventListener('livewire:navigating', function (event) {
- var kept = ['data-theme', 'data-theme-choice', 'data-theme-key', 'data-rail', 'data-rail-key'].map(function (name) {
+ var kept = ['data-scheme', 'data-theme', 'data-theme-choice', 'data-theme-key', 'data-rail', 'data-rail-key'].map(function (name) {
return [name, root.getAttribute(name)];
});
diff --git a/resources/views/showcase/layout.blade.php b/resources/views/showcase/layout.blade.php
index 0ebdd824..8ab85e58 100644
--- a/resources/views/showcase/layout.blade.php
+++ b/resources/views/showcase/layout.blade.php
@@ -132,6 +132,19 @@
+ {{-- With colour profiles, a preview of each on this page; nothing is stored. --}}
+ @if (($profiles = \NoNameWeb\LivewireMaterial\Support\Scheme::profiles()) !== [])
+
+
+
+
+
+ @foreach ($profiles as $name => $profile)
+
+ @endforeach
+
+ @endif
+
diff --git a/resources/views/showcase/sections/colour.blade.php b/resources/views/showcase/sections/colour.blade.php
index 58491f21..cef49cfc 100644
--- a/resources/views/showcase/sections/colour.blade.php
+++ b/resources/views/showcase/sections/colour.blade.php
@@ -10,6 +10,14 @@
'Surface' => ['bg-surface', 'bg-surface-dim', 'bg-surface-bright', 'bg-surface-container-lowest', 'bg-surface-container-low', 'bg-surface-container', 'bg-surface-container-high', 'bg-surface-container-highest', 'bg-on-surface', 'bg-on-surface-variant', 'bg-inverse-surface', 'bg-inverse-on-surface', 'bg-outline', 'bg-outline-variant'],
'Ink and lines' => ['bg-body', 'bg-meta', 'bg-quiet', 'bg-structure', 'bg-chrome', 'bg-divider'],
];
+
+ $examples = [
+ 'Colour profiles' => <<<'BLADE'
+
+
+
+ BLADE,
+ ];
@endphp
@@ -20,6 +28,16 @@
php artisan material:scheme. Both themes side by side, whatever the page is showing.
+
+ With colour profiles in livewire-material.profiles, the command generates each one under
+ <html data-scheme>, and <x-scheme-picker> chooses between them. Without
+ profiles the picker draws nothing.
+
+
+ @foreach ($examples as $title => $code)
+
+ @endforeach
+
@foreach (['light', 'dark'] as $theme)
diff --git a/src/Console/SchemeCommand.php b/src/Console/SchemeCommand.php
index 1af683f6..8d730df2 100644
--- a/src/Console/SchemeCommand.php
+++ b/src/Console/SchemeCommand.php
@@ -5,6 +5,7 @@ namespace NoNameWeb\LivewireMaterial\Console;
use Illuminate\Console\Command;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Facades\Process;
+use Illuminate\Support\Str;
use JsonException;
use Symfony\Component\Console\Attribute\AsCommand;
@@ -15,7 +16,7 @@ class SchemeCommand extends Command
* @var string
*/
protected $signature = 'material:scheme
- {seed : The source colour, as #rrggbb}
+ {seed? : The source colour, as #rrggbb; without it, every profile in livewire-material.profiles}
{--variant=tonal-spot : tonal-spot, vibrant, expressive, neutral, fidelity, content, monochrome, rainbow or fruit-salad}
{--contrast=0 : The contrast level, from -1 to 1}
{--success=#22a06b : The source of the success colour}
@@ -26,20 +27,73 @@ class SchemeCommand extends Command
/**
* @var string
*/
- protected $description = 'Generate the application\'s Material 3 colour scheme from a seed colour';
+ protected $description = 'Generate the application\'s Material 3 colour scheme from a seed colour, or every configured colour profile';
public function handle(Filesystem $files): int
{
$stylesheet = $this->option('output') ?: resource_path('css/material-scheme.css');
$data = preg_replace('/\.css$/', '', $stylesheet).'.json';
+ if (filled($this->argument('seed'))) {
+ $scheme = $this->generate((string) $this->argument('seed'), (string) $this->option('variant'), (float) $this->option('contrast'));
+
+ if ($scheme === null) {
+ return self::FAILURE;
+ }
+
+ return $this->write($files, $stylesheet, $data, $this->stylesheet($scheme), $scheme);
+ }
+
+ $profiles = config('livewire-material.profiles');
+
+ if (! is_array($profiles) || $profiles === []) {
+ $this->components->error('Give a seed colour (php artisan material:scheme "#4f46e5"), or list colour profiles in livewire-material.profiles.');
+
+ return self::FAILURE;
+ }
+
+ $generated = [];
+
+ foreach ($profiles as $name => $profile) {
+ if (! is_string($name) || preg_match('/^[a-z0-9-]+$/', $name) !== 1) {
+ $this->components->error("A profile's name is lowercase letters, digits and dashes; \"{$name}\" is not.");
+
+ return self::FAILURE;
+ }
+
+ $scheme = $this->generate((string) ($profile['seed'] ?? ''), (string) ($profile['variant'] ?? 'tonal-spot'), (float) ($profile['contrast'] ?? 0), "Profile \"{$name}\": ");
+
+ if ($scheme === null) {
+ return self::FAILURE;
+ }
+
+ $generated[$name] = ['label' => (string) ($profile['label'] ?? Str::headline($name)), ...$scheme];
+ }
+
+ $configured = config('livewire-material.profile');
+ $default = is_string($configured) && isset($generated[$configured]) ? $configured : array_key_first($generated);
+
+ return $this->write($files, $stylesheet, $data, $this->profilesStylesheet($generated, $default), [
+ ...collect($generated[$default])->except('label')->all(),
+ 'default' => $default,
+ 'profiles' => $generated,
+ ]);
+ }
+
+ /**
+ * One scheme from Google's colour utilities, or null once the reason has been shown.
+ *
+ * @return array{seed: string, variant: string, spec: string, contrast: float, light: array, dark: array}|null
+ */
+ protected function generate(string $seed, string $variant, float $contrast, string $context = ''): ?array
+ {
$result = Process::run([
config('livewire-material.node', 'node'),
__DIR__.'/../../resources/node/scheme.mjs',
json_encode([
- 'seed' => $this->argument('seed'),
- 'variant' => $this->option('variant'),
- 'contrast' => (float) $this->option('contrast'),
+ 'seed' => $seed,
+ 'variant' => $variant,
+ 'contrast' => $contrast,
'success' => $this->option('success'),
'warning' => $this->option('warning'),
'info' => $this->option('info'),
@@ -47,22 +101,27 @@ class SchemeCommand extends Command
]);
if ($result->failed()) {
- $this->components->error(trim($result->errorOutput()) ?: 'Node could not run the scheme generator. Is `node` installed and on the PATH?');
+ $this->components->error($context.(trim($result->errorOutput()) ?: 'Node could not run the scheme generator. Is `node` installed and on the PATH?'));
- return self::FAILURE;
+ return null;
}
try {
- /** @var array{seed: string, variant: string, spec: string, contrast: float, light: array, dark: array} $scheme */
- $scheme = json_decode($result->output(), true, flags: JSON_THROW_ON_ERROR);
+ return json_decode($result->output(), true, flags: JSON_THROW_ON_ERROR);
} catch (JsonException) {
- $this->components->error('The scheme generator answered with something other than JSON.');
+ $this->components->error($context.'The scheme generator answered with something other than JSON.');
- return self::FAILURE;
+ return null;
}
+ }
+ /**
+ * @param array $scheme
+ */
+ protected function write(Filesystem $files, string $stylesheet, string $data, string $css, array $scheme): int
+ {
$files->ensureDirectoryExists(dirname($stylesheet));
- $files->put($stylesheet, $this->stylesheet($scheme));
+ $files->put($stylesheet, $css);
$files->put($data, json_encode($scheme, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n");
$this->components->info("Wrote {$stylesheet} and {$data}.");
@@ -82,9 +141,7 @@ class SchemeCommand extends Command
$scheme['contrast'] != 0 ? ' --contrast='.$scheme['contrast'] : '',
);
- $block = fn (array $roles): string => collect($roles)
- ->map(fn (string $hex, string $role): string => " --md-sys-color-{$role}: {$hex};")
- ->implode("\n");
+ $blocks = $this->blocks([':root', "[data-theme='light']"], ["[data-theme='dark']"], $scheme);
return <<, dark: array}> $profiles
+ */
+ protected function profilesStylesheet(array $profiles, string $default): string
+ {
+ $list = collect($profiles)
+ ->map(fn (array $profile, string $name): string => sprintf(
+ ' * %-12s %s, %s%s',
+ $name,
+ $profile['seed'],
+ $profile['variant'],
+ $profile['contrast'] != 0 ? ', contrast '.$profile['contrast'] : '',
+ ))
+ ->implode("\n");
+
+ $css = <<blocks([':root', "[data-theme='light']"], ["[data-theme='dark']"], $profiles[$default]);
+
+ foreach ($profiles as $name => $profile) {
+ $css .= "\n".$this->blocks(
+ ["[data-scheme='{$name}']", "[data-scheme='{$name}'][data-theme='light']", "[data-scheme='{$name}'] [data-theme='light']"],
+ ["[data-scheme='{$name}'][data-theme='dark']", "[data-scheme='{$name}'] [data-theme='dark']"],
+ $profile,
+ );
+ }
+
+ return $css;
+ }
+
+ /**
+ * A light and a dark block of roles under the given selectors.
+ *
+ * @param list $light
+ * @param list $dark
+ * @param array{light: array, dark: array} $scheme
+ */
+ protected function blocks(array $light, array $dark, array $scheme): string
+ {
+ $roles = fn (array $roles): string => collect($roles)
+ ->map(fn (string $hex, string $role): string => " --md-sys-color-{$role}: {$hex};")
+ ->implode("\n");
+
+ return implode(",\n", $light)." {\n color-scheme: light;\n\n".$roles($scheme['light'])."\n}\n\n"
+ .implode(",\n", $dark)." {\n color-scheme: dark;\n\n".$roles($scheme['dark'])."\n}\n";
+ }
}
diff --git a/src/Support/Scheme.php b/src/Support/Scheme.php
index 7918ce0d..a4fdc900 100644
--- a/src/Support/Scheme.php
+++ b/src/Support/Scheme.php
@@ -2,9 +2,14 @@
namespace NoNameWeb\LivewireMaterial\Support;
+use Closure;
+use Illuminate\Support\Str;
+use Throwable;
+
/**
* The colour scheme as data, for the places CSS custom properties cannot reach: a mail client
- * resolves none, and an error page whose build is missing has no stylesheet to declare them.
+ * resolves none, an error page whose build is missing has no stylesheet to declare them, and the
+ * head script has to name the active colour profile before the stylesheet applies.
*
* The data is the JSON `php artisan material:scheme` writes beside the stylesheet
* (`livewire-material.scheme`, resources/css/material-scheme.json by default). Without that
@@ -13,18 +18,51 @@ namespace NoNameWeb\LivewireMaterial\Support;
* the role existed) is taken from the default, and a value that is not a #rrggbb hex is
* refused, so nothing but a colour ever reaches a stylesheet.
*
- * Read on every call and never kept: a regenerated scheme applies at once, and a long-running
- * worker holds nothing.
+ * A file generated from `livewire-material.profiles` holds every profile under `profiles`, its
+ * default under `default`, and the default's roles at the top level as a single scheme does. Which
+ * profile is active is the application's to say, through resolveProfileUsing(); the resolver is
+ * asked on every call, and a name it gives that is not in the file — or a resolver that throws, as
+ * one reading a database may while an error page for that very database renders — falls back to
+ * the file's default.
+ *
+ * Read on every call and never kept: a regenerated scheme or a newly chosen profile applies at
+ * once, and a long-running worker holds nothing but the resolver itself.
*/
class Scheme
{
/**
+ * @var (Closure(): ?string)|null
+ */
+ protected static ?Closure $profileResolver = null;
+
+ /**
+ * Name the active colour profile with the given closure, asked each time a colour is drawn.
+ *
+ * @param (Closure(): ?string)|null $resolver
+ */
+ public static function resolveProfileUsing(?Closure $resolver): void
+ {
+ static::$profileResolver = $resolver;
+ }
+
+ /**
+ * The roles to draw: the given profile's, the active profile's, or the single scheme's.
+ *
* @return array{light: array, dark: array}
*/
- public static function load(?string $path = null): array
+ public static function load(?string $path = null, ?string $profile = null): array
{
- $default = static::read(dirname(__DIR__, 2).'/resources/css/tokens/scheme.json');
- $scheme = static::read($path ?? (string) config('livewire-material.scheme'));
+ $data = static::data($path);
+ $profiles = static::profilesFrom($data);
+
+ if ($profiles !== []) {
+ $name = $profile !== null && isset($profiles[$profile]) ? $profile : static::activeFrom($data, $profiles);
+
+ return ['light' => $profiles[$name]['light'], 'dark' => $profiles[$name]['dark']];
+ }
+
+ $default = static::defaultScheme();
+ $scheme = ['light' => static::roles($data['light'] ?? null), 'dark' => static::roles($data['dark'] ?? null)];
return [
'light' => [...$default['light'], ...$scheme['light']],
@@ -37,22 +75,102 @@ class Scheme
*
* @return array
*/
- public static function light(?string $path = null): array
+ public static function light(?string $path = null, ?string $profile = null): array
{
- return static::load($path)['light'];
+ return static::load($path, $profile)['light'];
}
/**
- * @return array{light: array, dark: array}
+ * The generated colour profiles, in the order they were configured; empty for a single scheme.
+ *
+ * @return array, dark: array}>
*/
- protected static function read(string $path): array
+ public static function profiles(?string $path = null): array
{
+ return static::profilesFrom(static::data($path));
+ }
+
+ /**
+ * The active profile's name, or null for a single scheme.
+ */
+ public static function profile(?string $path = null): ?string
+ {
+ $data = static::data($path);
+ $profiles = static::profilesFrom($data);
+
+ return $profiles === [] ? null : static::activeFrom($data, $profiles);
+ }
+
+ /**
+ * @return array
+ */
+ protected static function data(?string $path): array
+ {
+ $path ??= (string) config('livewire-material.scheme');
$data = is_file($path) ? json_decode((string) file_get_contents($path), true) : null;
- return [
- 'light' => static::roles($data['light'] ?? null),
- 'dark' => static::roles($data['dark'] ?? null),
- ];
+ return is_array($data) ? $data : [];
+ }
+
+ /**
+ * @param array $data
+ * @return array, dark: array}>
+ */
+ protected static function profilesFrom(array $data): array
+ {
+ if (! is_array($data['profiles'] ?? null)) {
+ return [];
+ }
+
+ $default = static::defaultScheme();
+ $profiles = [];
+
+ foreach ($data['profiles'] as $name => $profile) {
+ if (! is_string($name) || preg_match('/^[a-z0-9-]+$/', $name) !== 1 || ! is_array($profile)) {
+ continue;
+ }
+
+ $profiles[$name] = [
+ 'label' => is_string($profile['label'] ?? null) ? $profile['label'] : Str::headline($name),
+ 'light' => [...$default['light'], ...static::roles($profile['light'] ?? null)],
+ 'dark' => [...$default['dark'], ...static::roles($profile['dark'] ?? null)],
+ ];
+ }
+
+ return $profiles;
+ }
+
+ /**
+ * @param array $data
+ * @param non-empty-array $profiles
+ */
+ protected static function activeFrom(array $data, array $profiles): string
+ {
+ try {
+ $resolved = static::$profileResolver ? (static::$profileResolver)() : null;
+ } catch (Throwable) {
+ $resolved = null;
+ }
+
+ foreach ([$resolved, $data['default'] ?? null] as $candidate) {
+ if (is_string($candidate) && isset($profiles[$candidate])) {
+ return $candidate;
+ }
+ }
+
+ return (string) array_key_first($profiles);
+ }
+
+ /**
+ * The package's own scheme, which fills any role a file lacks.
+ *
+ * @return array{light: array, dark: array}
+ */
+ protected static function defaultScheme(): array
+ {
+ $data = json_decode((string) file_get_contents(dirname(__DIR__, 2).'/resources/css/tokens/scheme.json'), true);
+
+ return ['light' => static::roles($data['light'] ?? null), 'dark' => static::roles($data['dark'] ?? null)];
}
/**
diff --git a/tests/Browser/ColourProfilesTest.php b/tests/Browser/ColourProfilesTest.php
new file mode 100644
index 00000000..cffd0ac2
--- /dev/null
+++ b/tests/Browser/ColourProfilesTest.php
@@ -0,0 +1,88 @@
+ workbench_path('resources/css/material-scheme.json')]);
+
+ $this->profiles = json_decode(file_get_contents(workbench_path('resources/css/material-scheme.json')), true)['profiles'];
+});
+
+afterEach(function () {
+ Scheme::resolveProfileUsing(null);
+});
+
+/**
+ * A #rrggbb hex as the rgb() a computed style reports.
+ */
+function rgb(string $hex): string
+{
+ [$red, $green, $blue] = sscanf($hex, '#%02x%02x%02x');
+
+ return "rgb({$red}, {$green}, {$blue})";
+}
+
+/**
+ * The page's primary colour role, as resolves it.
+ */
+function pagePrimary(): string
+{
+ return "getComputedStyle(document.documentElement).getPropertyValue('--md-sys-color-primary').trim()";
+}
+
+function profilesReady(mixed $page): mixed
+{
+ return $page->waitForEvent('networkidle')
+ ->assertScript("document.readyState === 'complete' && typeof window.Alpine !== 'undefined' && typeof window.Livewire !== 'undefined'");
+}
+
+it('draws each profile in light and dark, and the default without the attribute', function () {
+ $page = profilesReady(visit('/material/colour')->inLightMode())
+ ->assertScript("document.documentElement.getAttribute('data-scheme') === 'baseline'")
+ ->assertScript(pagePrimary()." === '{$this->profiles['baseline']['light']['primary']}'");
+
+ $page->script("document.documentElement.setAttribute('data-scheme', 'teal')");
+ $page->assertScript(pagePrimary()." === '{$this->profiles['teal']['light']['primary']}'");
+
+ $page->script("document.documentElement.setAttribute('data-theme', 'dark')");
+ $page->assertScript(pagePrimary()." === '{$this->profiles['teal']['dark']['primary']}'")
+ // A panel that sets its own theme keeps the page's profile.
+ ->assertScript("getComputedStyle(document.querySelector('#colour [data-theme=\"light\"] .bg-primary')).backgroundColor === '".rgb($this->profiles['teal']['light']['primary'])."'");
+
+ $page->script("document.documentElement.removeAttribute('data-scheme')");
+ $page->assertScript(pagePrimary()." === '{$this->profiles['baseline']['dark']['primary']}'")
+ ->assertNoJavaScriptErrors();
+});
+
+it('previews a profile from the picker and the showcase menu, and keeps it through wire:navigate', function () {
+ $page = profilesReady(visit('/material/colour')->inLightMode());
+
+ $page->click('#colour [data-scheme-option="rose"]')
+ ->assertScript("document.documentElement.getAttribute('data-scheme') === 'rose'")
+ ->assertScript(pagePrimary()." === '{$this->profiles['rose']['light']['primary']}'")
+ ->assertScript("window.eval(\"Alpine.store('theme').scheme\") === 'rose'");
+
+ $page->click('[data-test="showcase-profiles"] [aria-haspopup="menu"]')
+ ->click('[data-scheme-preview="teal"]')
+ ->assertScript("document.documentElement.getAttribute('data-scheme') === 'teal'");
+
+ $page->click('[data-navigation-rail-panel] a[href$="/material/buttons"]')
+ ->assertScript("location.pathname.endsWith('/material/buttons')")
+ ->assertScript("document.documentElement.getAttribute('data-scheme') === 'teal'")
+ ->assertNoJavaScriptErrors();
+});
+
+it('paints the profile the application resolves from the first frame', function () {
+ Scheme::resolveProfileUsing(fn (): string => 'rose');
+
+ profilesReady(visit('/material')->inDarkMode())
+ ->assertScript("document.documentElement.getAttribute('data-scheme') === 'rose'")
+ ->assertScript(pagePrimary()." === '{$this->profiles['rose']['dark']['primary']}'");
+});
diff --git a/tests/Feature/Components/SchemePickerTest.php b/tests/Feature/Components/SchemePickerTest.php
new file mode 100644
index 00000000..dea189fc
--- /dev/null
+++ b/tests/Feature/Components/SchemePickerTest.php
@@ -0,0 +1,60 @@
+path = sys_get_temp_dir().'/scheme-picker-'.uniqid().'.json';
+ File::put($this->path, json_encode([
+ 'default' => 'indigo',
+ 'profiles' => [
+ 'indigo' => ['label' => 'Indigo', 'light' => ['primary' => '#4f46e5', 'secondary' => '#5b5d72', 'tertiary' => '#77536d'], 'dark' => ['primary' => '#c0c1ff', 'secondary' => '#c4c5dd', 'tertiary' => '#e6bad7']],
+ 'teal' => ['label' => 'Teal', 'light' => ['primary' => '#00897b', 'secondary' => '#4a635f', 'tertiary' => '#456179'], 'dark' => ['primary' => '#80cbc4', 'secondary' => '#b1ccc6', 'tertiary' => '#adcae5']],
+ ],
+ ]));
+ config(['livewire-material.scheme' => $this->path]);
+});
+
+afterEach(function () {
+ File::delete($this->path);
+});
+
+it('draws a radio per generated profile, bound, named and previewing on change', function () {
+ $html = (string) $this->blade(' ');
+
+ expect($html)
+ ->toContain('Colour profile ')
+ ->toContain('Applies after saving')
+ ->toContain('data-scheme-option="indigo"')
+ ->toContain('data-scheme-option="teal"')
+ ->toMatch('/ toContain('x-on:change="$store.theme.previewScheme($event.target.value)"')
+ ->toContain('>Teal')
+ ->toContain('style="--swatch-light: #00897b; --swatch-dark: #80cbc4"');
+});
+
+it('keeps its inline styles to the scheme file\'s checked colours', function () {
+ File::put($this->path, json_encode([
+ 'default' => 'indigo',
+ 'profiles' => ['indigo' => ['label' => 'Indigo', 'light' => ['primary' => 'red; background: url(x)'], 'dark' => []]],
+ ]));
+
+ $html = (string) $this->blade(' ');
+
+ preg_match_all('/style="([^"]*)"/', $html, $styles);
+
+ expect($styles[1])->not->toBeEmpty()
+ ->each->toMatch('/^--swatch-light: #[0-9a-fA-F]{6}; --swatch-dark: #[0-9a-fA-F]{6}$/');
+});
+
+it('shows a validation message for the bound property instead of the hint', function () {
+ $this->withViewErrors(['colorProfile' => ['Choose one of the colour profiles.']])
+ ->blade(' ')
+ ->assertSee('Choose one of the colour profiles.')
+ ->assertDontSee('Applies after saving');
+});
+
+it('renders nothing for a single scheme', function () {
+ File::put($this->path, json_encode(['light' => ['primary' => '#123456'], 'dark' => []]));
+
+ expect(trim((string) $this->blade(' ')))->toBe('');
+});
diff --git a/tests/Feature/Components/ThemeScriptTest.php b/tests/Feature/Components/ThemeScriptTest.php
index 0dc0bd8a..424818ac 100644
--- a/tests/Feature/Components/ThemeScriptTest.php
+++ b/tests/Feature/Components/ThemeScriptTest.php
@@ -1,8 +1,11 @@
blade(' ')
- ->assertSee('({"default":"system","key":"material-theme","legacy":[],"rail":{"default":"expanded","key":"material-rail"}})', false);
+ ->assertSee('({"scheme":null,"default":"system","key":"material-theme","legacy":[],"rail":{"default":"expanded","key":"material-rail"}})', false);
});
it('takes the application\'s default, key and legacy keys', function () {
@@ -39,6 +42,28 @@ it('starts a collapsible rail as the application says, expanded otherwise', func
it('puts its attributes back on when wire:navigate swaps the page', function () {
$this->blade(' ')
->assertSee("document.addEventListener('livewire:navigating'", false)
- ->assertSee("['data-theme', 'data-theme-choice', 'data-theme-key', 'data-rail', 'data-rail-key']", false)
+ ->assertSee("['data-scheme', 'data-theme', 'data-theme-choice', 'data-theme-key', 'data-rail', 'data-rail-key']", false)
->assertSee('event.detail.onSwap(', false);
});
+
+it('names the active colour profile for , and none for a single scheme', function () {
+ $path = sys_get_temp_dir().'/theme-script-profiles-'.uniqid().'.json';
+ File::put($path, json_encode([
+ 'default' => 'indigo',
+ 'profiles' => ['indigo' => ['label' => 'Indigo', 'light' => [], 'dark' => []], 'teal' => ['label' => 'Teal', 'light' => [], 'dark' => []]],
+ ]));
+ config(['livewire-material.scheme' => $path]);
+
+ try {
+ $this->blade(' ')
+ ->assertSee('({"scheme":"indigo",', false)
+ ->assertSee("root.setAttribute('data-scheme', settings.scheme);", false);
+
+ Scheme::resolveProfileUsing(fn (): string => 'teal');
+
+ $this->blade(' ')->assertSee('({"scheme":"teal",', false);
+ } finally {
+ Scheme::resolveProfileUsing(null);
+ File::delete($path);
+ }
+});
diff --git a/tests/Feature/ErrorPagesTest.php b/tests/Feature/ErrorPagesTest.php
index 4f5800b1..9077cb1e 100644
--- a/tests/Feature/ErrorPagesTest.php
+++ b/tests/Feature/ErrorPagesTest.php
@@ -6,6 +6,7 @@ use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Vite;
use Illuminate\Support\Str;
use NoNameWeb\LivewireMaterial\LivewireMaterialServiceProvider;
+use NoNameWeb\LivewireMaterial\Support\Scheme;
beforeEach(function () {
$this->temporary = sys_get_temp_dir().'/livewire-material-errors-'.Str::random(8);
@@ -128,6 +129,33 @@ it('still renders, in the application\'s scheme, when the Vite manifest is missi
->assertDontSee('/build/', false);
});
+it('draws the active colour profile when the Vite manifest is missing', function () {
+ File::put($this->temporary.'/material-scheme.json', json_encode([
+ 'light' => ['primary' => '#4f46e5'],
+ 'dark' => ['primary' => '#aaaaff'],
+ 'default' => 'indigo',
+ 'profiles' => [
+ 'indigo' => ['label' => 'Indigo', 'light' => ['primary' => '#4f46e5'], 'dark' => ['primary' => '#aaaaff']],
+ 'teal' => ['label' => 'Teal', 'light' => ['primary' => '#00897b'], 'dark' => ['primary' => '#80cbc4']],
+ ],
+ ]));
+
+ config(['livewire-material.scheme' => $this->temporary.'/material-scheme.json']);
+ app()->usePublicPath($this->temporary);
+ Vite::useHotFile($this->temporary.'/hot');
+ Scheme::resolveProfileUsing(fn (): string => 'teal');
+
+ try {
+ $this->get('/abort/500')
+ ->assertStatus(500)
+ ->assertSee('--md-sys-color-primary: #00897b;', false)
+ ->assertSee('--md-sys-color-primary: #80cbc4;', false)
+ ->assertDontSee('#4f46e5', false);
+ } finally {
+ Scheme::resolveProfileUsing(null);
+ }
+});
+
it('shows the message an application passed for 403 and 503', function (int $code, string $message) {
withoutSkeletonErrorViews();
diff --git a/tests/Feature/MailThemeTest.php b/tests/Feature/MailThemeTest.php
index 051a6362..f7641a50 100644
--- a/tests/Feature/MailThemeTest.php
+++ b/tests/Feature/MailThemeTest.php
@@ -7,6 +7,7 @@ use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\View;
use Illuminate\Support\Str;
use NoNameWeb\LivewireMaterial\LivewireMaterialServiceProvider;
+use NoNameWeb\LivewireMaterial\Support\Scheme;
class ThemedProbeMail extends Mailable
{
@@ -113,6 +114,27 @@ it('inlines the application\'s scheme onto the mail', function () {
->not->toContain('color-mix');
});
+it('inlines the active colour profile onto the mail', function () {
+ File::put($this->temporary.'/material-scheme.json', json_encode([
+ 'light' => ['primary' => '#4f46e5'],
+ 'dark' => ['primary' => '#aaaaff'],
+ 'default' => 'indigo',
+ 'profiles' => [
+ 'indigo' => ['label' => 'Indigo', 'light' => ['primary' => '#4f46e5'], 'dark' => ['primary' => '#aaaaff']],
+ 'rose' => ['label' => 'Rose', 'light' => ['primary' => '#c2185b'], 'dark' => ['primary' => '#ffb0c8']],
+ ],
+ ]));
+
+ config(['livewire-material.scheme' => $this->temporary.'/material-scheme.json']);
+ Scheme::resolveProfileUsing(fn (): string => 'rose');
+
+ try {
+ expect(inlineStyle((new ThemedProbeMail)->render(), 'button-primary'))->toContain('background-color: #c2185b');
+ } finally {
+ Scheme::resolveProfileUsing(null);
+ }
+});
+
it('takes the theme from a mailable too', function () {
config(['mail.markdown.theme' => 'default']);
diff --git a/tests/Feature/SchemeCommandTest.php b/tests/Feature/SchemeCommandTest.php
index 01804dbc..d958812c 100644
--- a/tests/Feature/SchemeCommandTest.php
+++ b/tests/Feature/SchemeCommandTest.php
@@ -78,3 +78,68 @@ it('fails without writing anything when node cannot run', function () {
expect(File::exists($this->stylesheet))->toBeFalse();
});
+
+it('generates every configured profile into one stylesheet keyed by data-scheme', function () {
+ config([
+ 'livewire-material.profiles' => [
+ 'indigo' => ['label' => 'Indigo', 'seed' => '#4f46e5', 'variant' => 'vibrant'],
+ 'teal' => ['label' => 'Teal', 'seed' => '#00897b', 'variant' => 'vibrant'],
+ 'graphite' => ['seed' => '#5f6368', 'variant' => 'neutral'],
+ ],
+ 'livewire-material.profile' => 'teal',
+ ]);
+
+ $this->artisan('material:scheme', ['--output' => $this->stylesheet])->assertSuccessful();
+
+ $scheme = json_decode(File::get($this->data), true);
+ $stylesheet = File::get($this->stylesheet);
+
+ expect($scheme['default'])->toBe('teal')
+ ->and(array_keys($scheme['profiles']))->toBe(['indigo', 'teal', 'graphite'])
+ ->and($scheme['profiles']['graphite'])->label->toBe('Graphite')->variant->toBe('neutral')
+ ->and($scheme['profiles']['indigo']['light']['primary'])->not->toBe($scheme['profiles']['teal']['light']['primary'])
+ // The top level is the default profile, as a 1.0 reader expects.
+ ->and($scheme['light'])->toBe($scheme['profiles']['teal']['light'])
+ ->and($scheme['dark'])->toBe($scheme['profiles']['teal']['dark'])
+ ->and($scheme['seed'])->toBe('#00897b')
+ ->and($stylesheet)->toContain('php artisan material:scheme'."\n");
+
+ // The default's plain blocks come first, so a profile's single-attribute selector follows :root.
+ expect(strpos($stylesheet, ":root,\n[data-theme='light'] {"))->toBeLessThan(strpos($stylesheet, "[data-scheme='indigo'],"));
+
+ foreach ($scheme['profiles'] as $name => $profile) {
+ foreach (['light' => "[data-scheme='{$name}'] [data-theme='light'] {", 'dark' => "[data-scheme='{$name}'] [data-theme='dark'] {"] as $theme => $selector) {
+ $block = Str::of($stylesheet)->after($selector)->before('}')->toString();
+
+ expect($block)->toContain("color-scheme: {$theme};")
+ ->toContain("--md-sys-color-primary: {$profile[$theme]['primary']};")
+ ->toContain("--md-sys-color-surface: {$profile[$theme]['surface']};");
+ }
+ }
+});
+
+it('names the profile a generator error belongs to', function () {
+ config(['livewire-material.profiles' => ['indigo' => ['seed' => '#4f46e5'], 'broken' => ['seed' => 'teal']]]);
+
+ $this->artisan('material:scheme', ['--output' => $this->stylesheet])
+ ->expectsOutputToContain('Profile "broken"')
+ ->assertFailed();
+
+ expect(File::exists($this->stylesheet))->toBeFalse();
+});
+
+it('asks for a seed or profiles when it has neither', function () {
+ config(['livewire-material.profiles' => []]);
+
+ $this->artisan('material:scheme', ['--output' => $this->stylesheet])
+ ->expectsOutputToContain('livewire-material.profiles')
+ ->assertFailed();
+});
+
+it('refuses a profile name that cannot be an attribute value', function () {
+ config(['livewire-material.profiles' => ['Ocean Blue' => ['seed' => '#0b57d0']]]);
+
+ $this->artisan('material:scheme', ['--output' => $this->stylesheet])
+ ->expectsOutputToContain('"Ocean Blue" is not')
+ ->assertFailed();
+});
diff --git a/tests/Feature/SchemeTest.php b/tests/Feature/SchemeTest.php
new file mode 100644
index 00000000..befcc5ef
--- /dev/null
+++ b/tests/Feature/SchemeTest.php
@@ -0,0 +1,80 @@
+ $primaries profile => light primary
+ */
+function writeProfiles(string $path, array $primaries, string $default): void
+{
+ $profiles = collect($primaries)->map(fn (string $primary, string $name): array => [
+ 'label' => Str::headline($name),
+ 'seed' => $primary,
+ 'variant' => 'vibrant',
+ 'spec' => '2025',
+ 'contrast' => 0,
+ 'light' => ['primary' => $primary, 'surface' => '#fafafa'],
+ 'dark' => ['primary' => '#eeeeee', 'surface' => '#111111'],
+ ])->all();
+
+ File::put($path, json_encode([...collect($profiles[$default])->except('label')->all(), 'default' => $default, 'profiles' => $profiles]));
+}
+
+beforeEach(function () {
+ $this->path = sys_get_temp_dir().'/material-profiles-'.Str::random(8).'.json';
+ writeProfiles($this->path, ['indigo' => '#4f46e5', 'teal' => '#00897b', 'rose' => '#c2185b'], 'indigo');
+});
+
+afterEach(function () {
+ Scheme::resolveProfileUsing(null);
+ File::delete($this->path);
+});
+
+it('lists the generated profiles with their labels and roles, filling missing roles from the default', function () {
+ $profiles = Scheme::profiles($this->path);
+
+ expect(array_keys($profiles))->toBe(['indigo', 'teal', 'rose'])
+ ->and($profiles['teal']['label'])->toBe('Teal')
+ ->and($profiles['teal']['light']['primary'])->toBe('#00897b')
+ ->and($profiles['teal']['light'])->toHaveKey('on-surface-variant')
+ ->and(Scheme::profiles(sys_get_temp_dir().'/missing-scheme.json'))->toBe([]);
+});
+
+it('follows the resolver to the active profile, and falls back to the default', function () {
+ expect(Scheme::profile($this->path))->toBe('indigo');
+
+ Scheme::resolveProfileUsing(fn (): string => 'teal');
+ expect(Scheme::profile($this->path))->toBe('teal')
+ ->and(Scheme::light($this->path)['primary'])->toBe('#00897b')
+ ->and(Scheme::load($this->path)['dark']['surface'])->toBe('#111111');
+
+ Scheme::resolveProfileUsing(fn (): string => 'ocean');
+ expect(Scheme::profile($this->path))->toBe('indigo');
+
+ Scheme::resolveProfileUsing(fn (): ?string => null);
+ expect(Scheme::profile($this->path))->toBe('indigo');
+
+ Scheme::resolveProfileUsing(fn () => throw new RuntimeException('The database is down.'));
+ expect(Scheme::profile($this->path))->toBe('indigo');
+});
+
+it('loads a profile by name, whatever is active', function () {
+ Scheme::resolveProfileUsing(fn (): string => 'teal');
+
+ expect(Scheme::light($this->path, 'rose')['primary'])->toBe('#c2185b')
+ ->and(Scheme::light($this->path, 'ocean')['primary'])->toBe('#00897b');
+});
+
+it('has no profile for a single scheme, and loads it as before', function () {
+ File::put($this->path, json_encode(['light' => ['primary' => '#123456'], 'dark' => ['primary' => '#abcdef']]));
+ Scheme::resolveProfileUsing(fn (): string => 'teal');
+
+ expect(Scheme::profile($this->path))->toBeNull()
+ ->and(Scheme::profiles($this->path))->toBe([])
+ ->and(Scheme::light($this->path)['primary'])->toBe('#123456')
+ ->and(Scheme::light($this->path))->toHaveKey('on-surface');
+});
diff --git a/workbench/app/Providers/WorkbenchServiceProvider.php b/workbench/app/Providers/WorkbenchServiceProvider.php
index d5f6398b..d10c6bc3 100644
--- a/workbench/app/Providers/WorkbenchServiceProvider.php
+++ b/workbench/app/Providers/WorkbenchServiceProvider.php
@@ -23,6 +23,23 @@ class WorkbenchServiceProvider extends ServiceProvider
'workbench/resources/js/app.js',
]]);
+ // Three colour profiles, generated into workbench/resources/css/material-scheme.css with
+ // `vendor/bin/testbench material:scheme --output=workbench/resources/css/material-scheme.css`.
+ // Baseline is the package's own default, so the stylesheet draws it as before; the showcase
+ // offers the others. Tests start without profiles and point at the file when they need it.
+ config([
+ 'livewire-material.profiles' => [
+ 'baseline' => ['label' => 'Baseline', 'seed' => '#6750a4', 'variant' => 'tonal-spot'],
+ 'teal' => ['label' => 'Teal', 'seed' => '#00897b', 'variant' => 'vibrant'],
+ 'rose' => ['label' => 'Rose', 'seed' => '#c2185b', 'variant' => 'vibrant'],
+ ],
+ 'livewire-material.profile' => 'baseline',
+ ]);
+
+ if (! $this->app->runningUnitTests()) {
+ config(['livewire-material.scheme' => workbench_path('resources/css/material-scheme.json')]);
+ }
+
Vite::useHotFile(workbench_path('public/hot'));
}
}
diff --git a/workbench/resources/css/app.css b/workbench/resources/css/app.css
index bbf052bc..60797de7 100644
--- a/workbench/resources/css/app.css
+++ b/workbench/resources/css/app.css
@@ -2,6 +2,7 @@
sources so the plans in docs/ are not scanned for class names. */
@import 'tailwindcss' source(none);
@import '../../../resources/css/material.css';
+@import './material-scheme.css';
@source '../../../resources/views';
@source '../../../src';
diff --git a/workbench/resources/css/material-scheme.css b/workbench/resources/css/material-scheme.css
new file mode 100644
index 00000000..84e0a348
--- /dev/null
+++ b/workbench/resources/css/material-scheme.css
@@ -0,0 +1,586 @@
+/*
+ * Material 3 colour profiles, generated by Google's material-color-utilities (spec 2025).
+ *
+ * php artisan material:scheme
+ *
+ * From livewire-material.profiles. "baseline" is the default, and also stands without a
+ * data-scheme attribute:
+ *
+ * baseline #6750a4, tonal-spot
+ * teal #00897b, vibrant
+ * rose #c2185b, vibrant
+ *
+ * Regenerate rather than editing a value: every pair here (a role and its on-role) carries
+ * M3's contrast guarantee only as generated. The head script sets data-theme and data-scheme
+ * before the first paint.
+ */
+
+:root,
+[data-theme='light'] {
+ color-scheme: light;
+
+ --md-sys-color-background: #fdf7fe;
+ --md-sys-color-on-background: #34313a;
+ --md-sys-color-surface: #fdf7fe;
+ --md-sys-color-surface-dim: #ded8e4;
+ --md-sys-color-surface-bright: #fdf7fe;
+ --md-sys-color-surface-container-lowest: #ffffff;
+ --md-sys-color-surface-container-low: #f8f1fa;
+ --md-sys-color-surface-container: #f2ecf5;
+ --md-sys-color-surface-container-high: #ece6f0;
+ --md-sys-color-surface-container-highest: #e7e0ec;
+ --md-sys-color-on-surface: #34313a;
+ --md-sys-color-on-surface-variant: #615d68;
+ --md-sys-color-outline: #7d7983;
+ --md-sys-color-outline-variant: #b5b0bb;
+ --md-sys-color-inverse-surface: #0f0d12;
+ --md-sys-color-inverse-on-surface: #a09ba1;
+ --md-sys-color-primary: #655789;
+ --md-sys-color-primary-dim: #594b7c;
+ --md-sys-color-on-primary: #fdf7ff;
+ --md-sys-color-primary-container: #d4c3fd;
+ --md-sys-color-on-primary-container: #493c6c;
+ --md-sys-color-primary-fixed: #d4c3fd;
+ --md-sys-color-primary-fixed-dim: #c6b6ee;
+ --md-sys-color-on-primary-fixed: #352857;
+ --md-sys-color-on-primary-fixed-variant: #524576;
+ --md-sys-color-inverse-primary: #d4c3fd;
+ --md-sys-color-secondary: #625c71;
+ --md-sys-color-secondary-dim: #565065;
+ --md-sys-color-on-secondary: #fdf7ff;
+ --md-sys-color-secondary-container: #e8def8;
+ --md-sys-color-on-secondary-container: #554f63;
+ --md-sys-color-secondary-fixed: #e8def8;
+ --md-sys-color-secondary-fixed-dim: #dad0ea;
+ --md-sys-color-on-secondary-fixed: #423c50;
+ --md-sys-color-on-secondary-fixed-variant: #5f586e;
+ --md-sys-color-tertiary: #7b5270;
+ --md-sys-color-tertiary-dim: #6e4664;
+ --md-sys-color-on-tertiary: #fff7f9;
+ --md-sys-color-tertiary-container: #f4bfe3;
+ --md-sys-color-on-tertiary-container: #5f3956;
+ --md-sys-color-tertiary-fixed: #f4bfe3;
+ --md-sys-color-tertiary-fixed-dim: #e5b2d5;
+ --md-sys-color-on-tertiary-fixed: #4a2642;
+ --md-sys-color-on-tertiary-fixed-variant: #694260;
+ --md-sys-color-error: #a8364b;
+ --md-sys-color-error-dim: #6b0221;
+ --md-sys-color-on-error: #fff7f7;
+ --md-sys-color-error-container: #f97386;
+ --md-sys-color-on-error-container: #6e0523;
+ --md-sys-color-success: #006c45;
+ --md-sys-color-on-success: #ffffff;
+ --md-sys-color-success-container: #86f9bc;
+ --md-sys-color-on-success-container: #002112;
+ --md-sys-color-warning: #7c5800;
+ --md-sys-color-on-warning: #ffffff;
+ --md-sys-color-warning-container: #ffdea6;
+ --md-sys-color-on-warning-container: #271900;
+ --md-sys-color-info: #005ac4;
+ --md-sys-color-on-info: #ffffff;
+ --md-sys-color-info-container: #d8e2ff;
+ --md-sys-color-on-info-container: #001a42;
+ --md-sys-color-inverse-error: #f97386;
+ --md-sys-color-inverse-success: #69dca1;
+ --md-sys-color-inverse-warning: #fdbb28;
+ --md-sys-color-inverse-info: #aec6ff;
+}
+
+[data-theme='dark'] {
+ color-scheme: dark;
+
+ --md-sys-color-background: #0f0d12;
+ --md-sys-color-on-background: #eae3ef;
+ --md-sys-color-surface: #0f0d12;
+ --md-sys-color-surface-dim: #0f0d12;
+ --md-sys-color-surface-bright: #2e2b34;
+ --md-sys-color-surface-container-lowest: #000000;
+ --md-sys-color-surface-container-low: #141218;
+ --md-sys-color-surface-container: #1b181f;
+ --md-sys-color-surface-container-high: #211e26;
+ --md-sys-color-surface-container-highest: #27242d;
+ --md-sys-color-on-surface: #eae3ef;
+ --md-sys-color-on-surface-variant: #aea9b4;
+ --md-sys-color-outline: #78737e;
+ --md-sys-color-outline-variant: #4a4650;
+ --md-sys-color-inverse-surface: #fdf7fe;
+ --md-sys-color-inverse-on-surface: #575459;
+ --md-sys-color-primary: #cdc0ec;
+ --md-sys-color-primary-dim: #bfb2de;
+ --md-sys-color-on-primary: #443a5f;
+ --md-sys-color-primary-container: #574d72;
+ --md-sys-color-on-primary-container: #e9deff;
+ --md-sys-color-primary-fixed: #ded0fe;
+ --md-sys-color-primary-fixed-dim: #d0c3ef;
+ --md-sys-color-on-primary-fixed: #3c3256;
+ --md-sys-color-on-primary-fixed-variant: #594e74;
+ --md-sys-color-inverse-primary: #645980;
+ --md-sys-color-secondary: #cbc2db;
+ --md-sys-color-secondary-dim: #beb5cd;
+ --md-sys-color-on-secondary: #433d51;
+ --md-sys-color-secondary-container: #3e384c;
+ --md-sys-color-on-secondary-container: #c4bbd4;
+ --md-sys-color-secondary-fixed: #e8def8;
+ --md-sys-color-secondary-fixed-dim: #dad0ea;
+ --md-sys-color-on-secondary-fixed: #423c50;
+ --md-sys-color-on-secondary-fixed-variant: #5f586e;
+ --md-sys-color-tertiary: #ffcfef;
+ --md-sys-color-tertiary-dim: #f4bfe3;
+ --md-sys-color-on-tertiary: #69415f;
+ --md-sys-color-tertiary-container: #f4bfe3;
+ --md-sys-color-on-tertiary-container: #5f3956;
+ --md-sys-color-tertiary-fixed: #f4bfe3;
+ --md-sys-color-tertiary-fixed-dim: #e5b2d5;
+ --md-sys-color-on-tertiary-fixed: #4a2642;
+ --md-sys-color-on-tertiary-fixed-variant: #694260;
+ --md-sys-color-error: #f97386;
+ --md-sys-color-error-dim: #c44b5f;
+ --md-sys-color-on-error: #490013;
+ --md-sys-color-error-container: #871c34;
+ --md-sys-color-on-error-container: #ff97a3;
+ --md-sys-color-success: #69dca1;
+ --md-sys-color-on-success: #003822;
+ --md-sys-color-success-container: #005233;
+ --md-sys-color-on-success-container: #86f9bc;
+ --md-sys-color-warning: #fdbb28;
+ --md-sys-color-on-warning: #412d00;
+ --md-sys-color-warning-container: #5e4200;
+ --md-sys-color-on-warning-container: #ffdea6;
+ --md-sys-color-info: #aec6ff;
+ --md-sys-color-on-info: #002e6a;
+ --md-sys-color-info-container: #004396;
+ --md-sys-color-on-info-container: #d8e2ff;
+ --md-sys-color-inverse-error: #a8364b;
+ --md-sys-color-inverse-success: #006c45;
+ --md-sys-color-inverse-warning: #7c5800;
+ --md-sys-color-inverse-info: #005ac4;
+}
+
+[data-scheme='baseline'],
+[data-scheme='baseline'][data-theme='light'],
+[data-scheme='baseline'] [data-theme='light'] {
+ color-scheme: light;
+
+ --md-sys-color-background: #fdf7fe;
+ --md-sys-color-on-background: #34313a;
+ --md-sys-color-surface: #fdf7fe;
+ --md-sys-color-surface-dim: #ded8e4;
+ --md-sys-color-surface-bright: #fdf7fe;
+ --md-sys-color-surface-container-lowest: #ffffff;
+ --md-sys-color-surface-container-low: #f8f1fa;
+ --md-sys-color-surface-container: #f2ecf5;
+ --md-sys-color-surface-container-high: #ece6f0;
+ --md-sys-color-surface-container-highest: #e7e0ec;
+ --md-sys-color-on-surface: #34313a;
+ --md-sys-color-on-surface-variant: #615d68;
+ --md-sys-color-outline: #7d7983;
+ --md-sys-color-outline-variant: #b5b0bb;
+ --md-sys-color-inverse-surface: #0f0d12;
+ --md-sys-color-inverse-on-surface: #a09ba1;
+ --md-sys-color-primary: #655789;
+ --md-sys-color-primary-dim: #594b7c;
+ --md-sys-color-on-primary: #fdf7ff;
+ --md-sys-color-primary-container: #d4c3fd;
+ --md-sys-color-on-primary-container: #493c6c;
+ --md-sys-color-primary-fixed: #d4c3fd;
+ --md-sys-color-primary-fixed-dim: #c6b6ee;
+ --md-sys-color-on-primary-fixed: #352857;
+ --md-sys-color-on-primary-fixed-variant: #524576;
+ --md-sys-color-inverse-primary: #d4c3fd;
+ --md-sys-color-secondary: #625c71;
+ --md-sys-color-secondary-dim: #565065;
+ --md-sys-color-on-secondary: #fdf7ff;
+ --md-sys-color-secondary-container: #e8def8;
+ --md-sys-color-on-secondary-container: #554f63;
+ --md-sys-color-secondary-fixed: #e8def8;
+ --md-sys-color-secondary-fixed-dim: #dad0ea;
+ --md-sys-color-on-secondary-fixed: #423c50;
+ --md-sys-color-on-secondary-fixed-variant: #5f586e;
+ --md-sys-color-tertiary: #7b5270;
+ --md-sys-color-tertiary-dim: #6e4664;
+ --md-sys-color-on-tertiary: #fff7f9;
+ --md-sys-color-tertiary-container: #f4bfe3;
+ --md-sys-color-on-tertiary-container: #5f3956;
+ --md-sys-color-tertiary-fixed: #f4bfe3;
+ --md-sys-color-tertiary-fixed-dim: #e5b2d5;
+ --md-sys-color-on-tertiary-fixed: #4a2642;
+ --md-sys-color-on-tertiary-fixed-variant: #694260;
+ --md-sys-color-error: #a8364b;
+ --md-sys-color-error-dim: #6b0221;
+ --md-sys-color-on-error: #fff7f7;
+ --md-sys-color-error-container: #f97386;
+ --md-sys-color-on-error-container: #6e0523;
+ --md-sys-color-success: #006c45;
+ --md-sys-color-on-success: #ffffff;
+ --md-sys-color-success-container: #86f9bc;
+ --md-sys-color-on-success-container: #002112;
+ --md-sys-color-warning: #7c5800;
+ --md-sys-color-on-warning: #ffffff;
+ --md-sys-color-warning-container: #ffdea6;
+ --md-sys-color-on-warning-container: #271900;
+ --md-sys-color-info: #005ac4;
+ --md-sys-color-on-info: #ffffff;
+ --md-sys-color-info-container: #d8e2ff;
+ --md-sys-color-on-info-container: #001a42;
+ --md-sys-color-inverse-error: #f97386;
+ --md-sys-color-inverse-success: #69dca1;
+ --md-sys-color-inverse-warning: #fdbb28;
+ --md-sys-color-inverse-info: #aec6ff;
+}
+
+[data-scheme='baseline'][data-theme='dark'],
+[data-scheme='baseline'] [data-theme='dark'] {
+ color-scheme: dark;
+
+ --md-sys-color-background: #0f0d12;
+ --md-sys-color-on-background: #eae3ef;
+ --md-sys-color-surface: #0f0d12;
+ --md-sys-color-surface-dim: #0f0d12;
+ --md-sys-color-surface-bright: #2e2b34;
+ --md-sys-color-surface-container-lowest: #000000;
+ --md-sys-color-surface-container-low: #141218;
+ --md-sys-color-surface-container: #1b181f;
+ --md-sys-color-surface-container-high: #211e26;
+ --md-sys-color-surface-container-highest: #27242d;
+ --md-sys-color-on-surface: #eae3ef;
+ --md-sys-color-on-surface-variant: #aea9b4;
+ --md-sys-color-outline: #78737e;
+ --md-sys-color-outline-variant: #4a4650;
+ --md-sys-color-inverse-surface: #fdf7fe;
+ --md-sys-color-inverse-on-surface: #575459;
+ --md-sys-color-primary: #cdc0ec;
+ --md-sys-color-primary-dim: #bfb2de;
+ --md-sys-color-on-primary: #443a5f;
+ --md-sys-color-primary-container: #574d72;
+ --md-sys-color-on-primary-container: #e9deff;
+ --md-sys-color-primary-fixed: #ded0fe;
+ --md-sys-color-primary-fixed-dim: #d0c3ef;
+ --md-sys-color-on-primary-fixed: #3c3256;
+ --md-sys-color-on-primary-fixed-variant: #594e74;
+ --md-sys-color-inverse-primary: #645980;
+ --md-sys-color-secondary: #cbc2db;
+ --md-sys-color-secondary-dim: #beb5cd;
+ --md-sys-color-on-secondary: #433d51;
+ --md-sys-color-secondary-container: #3e384c;
+ --md-sys-color-on-secondary-container: #c4bbd4;
+ --md-sys-color-secondary-fixed: #e8def8;
+ --md-sys-color-secondary-fixed-dim: #dad0ea;
+ --md-sys-color-on-secondary-fixed: #423c50;
+ --md-sys-color-on-secondary-fixed-variant: #5f586e;
+ --md-sys-color-tertiary: #ffcfef;
+ --md-sys-color-tertiary-dim: #f4bfe3;
+ --md-sys-color-on-tertiary: #69415f;
+ --md-sys-color-tertiary-container: #f4bfe3;
+ --md-sys-color-on-tertiary-container: #5f3956;
+ --md-sys-color-tertiary-fixed: #f4bfe3;
+ --md-sys-color-tertiary-fixed-dim: #e5b2d5;
+ --md-sys-color-on-tertiary-fixed: #4a2642;
+ --md-sys-color-on-tertiary-fixed-variant: #694260;
+ --md-sys-color-error: #f97386;
+ --md-sys-color-error-dim: #c44b5f;
+ --md-sys-color-on-error: #490013;
+ --md-sys-color-error-container: #871c34;
+ --md-sys-color-on-error-container: #ff97a3;
+ --md-sys-color-success: #69dca1;
+ --md-sys-color-on-success: #003822;
+ --md-sys-color-success-container: #005233;
+ --md-sys-color-on-success-container: #86f9bc;
+ --md-sys-color-warning: #fdbb28;
+ --md-sys-color-on-warning: #412d00;
+ --md-sys-color-warning-container: #5e4200;
+ --md-sys-color-on-warning-container: #ffdea6;
+ --md-sys-color-info: #aec6ff;
+ --md-sys-color-on-info: #002e6a;
+ --md-sys-color-info-container: #004396;
+ --md-sys-color-on-info-container: #d8e2ff;
+ --md-sys-color-inverse-error: #a8364b;
+ --md-sys-color-inverse-success: #006c45;
+ --md-sys-color-inverse-warning: #7c5800;
+ --md-sys-color-inverse-info: #005ac4;
+}
+
+[data-scheme='teal'],
+[data-scheme='teal'][data-theme='light'],
+[data-scheme='teal'] [data-theme='light'] {
+ color-scheme: light;
+
+ --md-sys-color-background: #d3fffd;
+ --md-sys-color-on-background: #003534;
+ --md-sys-color-surface: #d3fffd;
+ --md-sys-color-surface-dim: #87e4e1;
+ --md-sys-color-surface-bright: #d3fffd;
+ --md-sys-color-surface-container-lowest: #ffffff;
+ --md-sys-color-surface-container-low: #bafdfa;
+ --md-sys-color-surface-container: #adf5f2;
+ --md-sys-color-surface-container-high: #a2f0ed;
+ --md-sys-color-surface-container-highest: #96ece8;
+ --md-sys-color-on-surface: #003534;
+ --md-sys-color-on-surface-variant: #296463;
+ --md-sys-color-outline: #46807e;
+ --md-sys-color-outline-variant: #7db7b5;
+ --md-sys-color-inverse-surface: #001111;
+ --md-sys-color-inverse-on-surface: #6da7a5;
+ --md-sys-color-primary: #00675c;
+ --md-sys-color-primary-dim: #005a50;
+ --md-sys-color-on-primary: #c0fff3;
+ --md-sys-color-primary-container: #00f7df;
+ --md-sys-color-on-primary-container: #00594f;
+ --md-sys-color-primary-fixed: #00f7df;
+ --md-sys-color-primary-fixed-dim: #00e8d1;
+ --md-sys-color-on-primary-fixed: #00443c;
+ --md-sys-color-on-primary-fixed-variant: #006359;
+ --md-sys-color-inverse-primary: #00fee5;
+ --md-sys-color-secondary: #006765;
+ --md-sys-color-secondary-dim: #005958;
+ --md-sys-color-on-secondary: #bcfffc;
+ --md-sys-color-secondary-container: #38fbf7;
+ --md-sys-color-on-secondary-container: #005c5a;
+ --md-sys-color-secondary-fixed: #38fbf7;
+ --md-sys-color-secondary-fixed-dim: #10ece8;
+ --md-sys-color-on-secondary-fixed: #004746;
+ --md-sys-color-on-secondary-fixed-variant: #006765;
+ --md-sys-color-tertiary: #006386;
+ --md-sys-color-tertiary-dim: #005675;
+ --md-sys-color-on-tertiary: #e7f5ff;
+ --md-sys-color-tertiary-container: #20c0ff;
+ --md-sys-color-on-tertiary-container: #00374d;
+ --md-sys-color-tertiary-fixed: #20c0ff;
+ --md-sys-color-tertiary-fixed-dim: #00b2ee;
+ --md-sys-color-on-tertiary-fixed: #001e2b;
+ --md-sys-color-on-tertiary-fixed-variant: #004059;
+ --md-sys-color-error: #b31b25;
+ --md-sys-color-error-dim: #9f0519;
+ --md-sys-color-on-error: #ffefee;
+ --md-sys-color-error-container: #fb5151;
+ --md-sys-color-on-error-container: #570008;
+ --md-sys-color-success: #006c45;
+ --md-sys-color-on-success: #ffffff;
+ --md-sys-color-success-container: #86f9bc;
+ --md-sys-color-on-success-container: #002112;
+ --md-sys-color-warning: #7c5800;
+ --md-sys-color-on-warning: #ffffff;
+ --md-sys-color-warning-container: #ffdea6;
+ --md-sys-color-on-warning-container: #271900;
+ --md-sys-color-info: #005ac4;
+ --md-sys-color-on-info: #ffffff;
+ --md-sys-color-info-container: #d8e2ff;
+ --md-sys-color-on-info-container: #001a42;
+ --md-sys-color-inverse-error: #ff716c;
+ --md-sys-color-inverse-success: #69dca1;
+ --md-sys-color-inverse-warning: #fdbb28;
+ --md-sys-color-inverse-info: #aec6ff;
+}
+
+[data-scheme='teal'][data-theme='dark'],
+[data-scheme='teal'] [data-theme='dark'] {
+ color-scheme: dark;
+
+ --md-sys-color-background: #001111;
+ --md-sys-color-on-background: #b8f3f1;
+ --md-sys-color-surface: #001111;
+ --md-sys-color-surface-dim: #001111;
+ --md-sys-color-surface-bright: #003231;
+ --md-sys-color-surface-container-lowest: #000000;
+ --md-sys-color-surface-container-low: #001716;
+ --md-sys-color-surface-container: #001e1d;
+ --md-sys-color-surface-container-high: #002424;
+ --md-sys-color-surface-container-highest: #002b2a;
+ --md-sys-color-on-surface: #b8f3f1;
+ --md-sys-color-on-surface-variant: #7bb5b3;
+ --md-sys-color-outline: #457f7d;
+ --md-sys-color-outline-variant: #0e504f;
+ --md-sys-color-inverse-surface: #e3fffd;
+ --md-sys-color-inverse-on-surface: #215e5c;
+ --md-sys-color-primary: #b4fff1;
+ --md-sys-color-primary-dim: #00fee5;
+ --md-sys-color-on-primary: #00665b;
+ --md-sys-color-primary-container: #00fee5;
+ --md-sys-color-on-primary-container: #005c53;
+ --md-sys-color-primary-fixed: #00f7df;
+ --md-sys-color-primary-fixed-dim: #00e8d1;
+ --md-sys-color-on-primary-fixed: #00443c;
+ --md-sys-color-on-primary-fixed-variant: #006359;
+ --md-sys-color-inverse-primary: #006b60;
+ --md-sys-color-secondary: #38fbf7;
+ --md-sys-color-secondary-dim: #10ece8;
+ --md-sys-color-on-secondary: #005c5a;
+ --md-sys-color-secondary-container: #006a68;
+ --md-sys-color-on-secondary-container: #dafffd;
+ --md-sys-color-secondary-fixed: #38fbf7;
+ --md-sys-color-secondary-fixed-dim: #10ece8;
+ --md-sys-color-on-secondary-fixed: #004746;
+ --md-sys-color-on-secondary-fixed-variant: #006765;
+ --md-sys-color-tertiary: #68ccff;
+ --md-sys-color-tertiary-dim: #20c0ff;
+ --md-sys-color-on-tertiary: #00415a;
+ --md-sys-color-tertiary-container: #20c0ff;
+ --md-sys-color-on-tertiary-container: #00374d;
+ --md-sys-color-tertiary-fixed: #20c0ff;
+ --md-sys-color-tertiary-fixed-dim: #00b2ee;
+ --md-sys-color-on-tertiary-fixed: #001e2b;
+ --md-sys-color-on-tertiary-fixed-variant: #004059;
+ --md-sys-color-error: #ff716c;
+ --md-sys-color-error-dim: #d7383b;
+ --md-sys-color-on-error: #490006;
+ --md-sys-color-error-container: #9f0519;
+ --md-sys-color-on-error-container: #ffa8a3;
+ --md-sys-color-success: #69dca1;
+ --md-sys-color-on-success: #003822;
+ --md-sys-color-success-container: #005233;
+ --md-sys-color-on-success-container: #86f9bc;
+ --md-sys-color-warning: #fdbb28;
+ --md-sys-color-on-warning: #412d00;
+ --md-sys-color-warning-container: #5e4200;
+ --md-sys-color-on-warning-container: #ffdea6;
+ --md-sys-color-info: #aec6ff;
+ --md-sys-color-on-info: #002e6a;
+ --md-sys-color-info-container: #004396;
+ --md-sys-color-on-info-container: #d8e2ff;
+ --md-sys-color-inverse-error: #b31b25;
+ --md-sys-color-inverse-success: #006c45;
+ --md-sys-color-inverse-warning: #7c5800;
+ --md-sys-color-inverse-info: #005ac4;
+}
+
+[data-scheme='rose'],
+[data-scheme='rose'][data-theme='light'],
+[data-scheme='rose'] [data-theme='light'] {
+ color-scheme: light;
+
+ --md-sys-color-background: #fff4f6;
+ --md-sys-color-on-background: #492136;
+ --md-sys-color-surface: #fff4f6;
+ --md-sys-color-surface-dim: #ffc4de;
+ --md-sys-color-surface-bright: #fff4f6;
+ --md-sys-color-surface-container-lowest: #ffffff;
+ --md-sys-color-surface-container-low: #ffecf2;
+ --md-sys-color-surface-container: #ffe0ec;
+ --md-sys-color-surface-container-high: #ffd8e8;
+ --md-sys-color-surface-container-highest: #ffd0e4;
+ --md-sys-color-on-surface: #492136;
+ --md-sys-color-on-surface-variant: #7c4d64;
+ --md-sys-color-outline: #9b6880;
+ --md-sys-color-outline-variant: #d69db7;
+ --md-sys-color-inverse-surface: #220215;
+ --md-sys-color-inverse-on-surface: #c58da6;
+ --md-sys-color-primary: #ae1d53;
+ --md-sys-color-primary-dim: #9e0b47;
+ --md-sys-color-on-primary: #ffeff0;
+ --md-sys-color-primary-container: #ff7198;
+ --md-sys-color-on-primary-container: #4d001e;
+ --md-sys-color-primary-fixed: #ff7198;
+ --md-sys-color-primary-fixed-dim: #f95a8a;
+ --md-sys-color-on-primary-fixed: #000000;
+ --md-sys-color-on-primary-fixed-variant: #5e0027;
+ --md-sys-color-inverse-primary: #f65787;
+ --md-sys-color-secondary: #99366e;
+ --md-sys-color-secondary-dim: #8a2a62;
+ --md-sys-color-on-secondary: #ffeff3;
+ --md-sys-color-secondary-container: #ffc0dc;
+ --md-sys-color-on-secondary-container: #7f2159;
+ --md-sys-color-secondary-fixed: #ffc0dc;
+ --md-sys-color-secondary-fixed-dim: #ffabd2;
+ --md-sys-color-on-secondary-fixed: #670845;
+ --md-sys-color-on-secondary-fixed-variant: #8b2b63;
+ --md-sys-color-tertiary: #5c4bb4;
+ --md-sys-color-tertiary-dim: #503ea7;
+ --md-sys-color-on-tertiary: #f6f0ff;
+ --md-sys-color-tertiary-container: #b3a5ff;
+ --md-sys-color-on-tertiary-container: #301887;
+ --md-sys-color-tertiary-fixed: #b3a5ff;
+ --md-sys-color-tertiary-fixed-dim: #a595ff;
+ --md-sys-color-on-tertiary-fixed: #170059;
+ --md-sys-color-on-tertiary-fixed-variant: #39248f;
+ --md-sys-color-error: #b31b25;
+ --md-sys-color-error-dim: #9f0519;
+ --md-sys-color-on-error: #ffefee;
+ --md-sys-color-error-container: #fb5151;
+ --md-sys-color-on-error-container: #570008;
+ --md-sys-color-success: #006c45;
+ --md-sys-color-on-success: #ffffff;
+ --md-sys-color-success-container: #86f9bc;
+ --md-sys-color-on-success-container: #002112;
+ --md-sys-color-warning: #7c5800;
+ --md-sys-color-on-warning: #ffffff;
+ --md-sys-color-warning-container: #ffdea6;
+ --md-sys-color-on-warning-container: #271900;
+ --md-sys-color-info: #005ac4;
+ --md-sys-color-on-info: #ffffff;
+ --md-sys-color-info-container: #d8e2ff;
+ --md-sys-color-on-info-container: #001a42;
+ --md-sys-color-inverse-error: #ff716c;
+ --md-sys-color-inverse-success: #69dca1;
+ --md-sys-color-inverse-warning: #fdbb28;
+ --md-sys-color-inverse-info: #aec6ff;
+}
+
+[data-scheme='rose'][data-theme='dark'],
+[data-scheme='rose'] [data-theme='dark'] {
+ color-scheme: dark;
+
+ --md-sys-color-background: #220215;
+ --md-sys-color-on-background: #ffdcea;
+ --md-sys-color-surface: #220215;
+ --md-sys-color-surface-dim: #220215;
+ --md-sys-color-surface-bright: #4e1737;
+ --md-sys-color-surface-container-lowest: #000000;
+ --md-sys-color-surface-container-low: #2a041b;
+ --md-sys-color-surface-container: #330822;
+ --md-sys-color-surface-container-high: #3c0d28;
+ --md-sys-color-surface-container-highest: #45122f;
+ --md-sys-color-on-surface: #ffdcea;
+ --md-sys-color-on-surface-variant: #d49bb4;
+ --md-sys-color-outline: #99667e;
+ --md-sys-color-outline-variant: #663a50;
+ --md-sys-color-inverse-surface: #fff8f8;
+ --md-sys-color-inverse-on-surface: #75475d;
+ --md-sys-color-primary: #ff8aa7;
+ --md-sys-color-primary-dim: #ff6b95;
+ --md-sys-color-on-primary: #620029;
+ --md-sys-color-primary-container: #ff7198;
+ --md-sys-color-on-primary-container: #4d001e;
+ --md-sys-color-primary-fixed: #ff7198;
+ --md-sys-color-primary-fixed-dim: #f95a8a;
+ --md-sys-color-on-primary-fixed: #000000;
+ --md-sys-color-on-primary-fixed-variant: #5e0027;
+ --md-sys-color-inverse-primary: #b32257;
+ --md-sys-color-secondary: #ff8ac6;
+ --md-sys-color-secondary-dim: #ef7db9;
+ --md-sys-color-on-secondary: #620241;
+ --md-sys-color-secondary-container: #7f2058;
+ --md-sys-color-on-secondary-container: #ffbedb;
+ --md-sys-color-secondary-fixed: #ffc0dc;
+ --md-sys-color-secondary-fixed-dim: #ffabd2;
+ --md-sys-color-on-secondary-fixed: #670845;
+ --md-sys-color-on-secondary-fixed-variant: #8b2b63;
+ --md-sys-color-tertiary: #aa9bff;
+ --md-sys-color-tertiary-dim: #9f8ffc;
+ --md-sys-color-on-tertiary: #290c80;
+ --md-sys-color-tertiary-container: #9d8cfa;
+ --md-sys-color-on-tertiary-container: #1c0068;
+ --md-sys-color-tertiary-fixed: #b3a5ff;
+ --md-sys-color-tertiary-fixed-dim: #a595ff;
+ --md-sys-color-on-tertiary-fixed: #170059;
+ --md-sys-color-on-tertiary-fixed-variant: #39248f;
+ --md-sys-color-error: #ff716c;
+ --md-sys-color-error-dim: #d7383b;
+ --md-sys-color-on-error: #490006;
+ --md-sys-color-error-container: #9f0519;
+ --md-sys-color-on-error-container: #ffa8a3;
+ --md-sys-color-success: #69dca1;
+ --md-sys-color-on-success: #003822;
+ --md-sys-color-success-container: #005233;
+ --md-sys-color-on-success-container: #86f9bc;
+ --md-sys-color-warning: #fdbb28;
+ --md-sys-color-on-warning: #412d00;
+ --md-sys-color-warning-container: #5e4200;
+ --md-sys-color-on-warning-container: #ffdea6;
+ --md-sys-color-info: #aec6ff;
+ --md-sys-color-on-info: #002e6a;
+ --md-sys-color-info-container: #004396;
+ --md-sys-color-on-info-container: #d8e2ff;
+ --md-sys-color-inverse-error: #b31b25;
+ --md-sys-color-inverse-success: #006c45;
+ --md-sys-color-inverse-warning: #7c5800;
+ --md-sys-color-inverse-info: #005ac4;
+}
diff --git a/workbench/resources/css/material-scheme.json b/workbench/resources/css/material-scheme.json
new file mode 100644
index 00000000..560bf52c
--- /dev/null
+++ b/workbench/resources/css/material-scheme.json
@@ -0,0 +1,566 @@
+{
+ "seed": "#6750a4",
+ "variant": "tonal-spot",
+ "spec": "2025",
+ "contrast": 0,
+ "light": {
+ "background": "#fdf7fe",
+ "on-background": "#34313a",
+ "surface": "#fdf7fe",
+ "surface-dim": "#ded8e4",
+ "surface-bright": "#fdf7fe",
+ "surface-container-lowest": "#ffffff",
+ "surface-container-low": "#f8f1fa",
+ "surface-container": "#f2ecf5",
+ "surface-container-high": "#ece6f0",
+ "surface-container-highest": "#e7e0ec",
+ "on-surface": "#34313a",
+ "on-surface-variant": "#615d68",
+ "outline": "#7d7983",
+ "outline-variant": "#b5b0bb",
+ "inverse-surface": "#0f0d12",
+ "inverse-on-surface": "#a09ba1",
+ "primary": "#655789",
+ "primary-dim": "#594b7c",
+ "on-primary": "#fdf7ff",
+ "primary-container": "#d4c3fd",
+ "on-primary-container": "#493c6c",
+ "primary-fixed": "#d4c3fd",
+ "primary-fixed-dim": "#c6b6ee",
+ "on-primary-fixed": "#352857",
+ "on-primary-fixed-variant": "#524576",
+ "inverse-primary": "#d4c3fd",
+ "secondary": "#625c71",
+ "secondary-dim": "#565065",
+ "on-secondary": "#fdf7ff",
+ "secondary-container": "#e8def8",
+ "on-secondary-container": "#554f63",
+ "secondary-fixed": "#e8def8",
+ "secondary-fixed-dim": "#dad0ea",
+ "on-secondary-fixed": "#423c50",
+ "on-secondary-fixed-variant": "#5f586e",
+ "tertiary": "#7b5270",
+ "tertiary-dim": "#6e4664",
+ "on-tertiary": "#fff7f9",
+ "tertiary-container": "#f4bfe3",
+ "on-tertiary-container": "#5f3956",
+ "tertiary-fixed": "#f4bfe3",
+ "tertiary-fixed-dim": "#e5b2d5",
+ "on-tertiary-fixed": "#4a2642",
+ "on-tertiary-fixed-variant": "#694260",
+ "error": "#a8364b",
+ "error-dim": "#6b0221",
+ "on-error": "#fff7f7",
+ "error-container": "#f97386",
+ "on-error-container": "#6e0523",
+ "success": "#006c45",
+ "on-success": "#ffffff",
+ "success-container": "#86f9bc",
+ "on-success-container": "#002112",
+ "warning": "#7c5800",
+ "on-warning": "#ffffff",
+ "warning-container": "#ffdea6",
+ "on-warning-container": "#271900",
+ "info": "#005ac4",
+ "on-info": "#ffffff",
+ "info-container": "#d8e2ff",
+ "on-info-container": "#001a42",
+ "inverse-error": "#f97386",
+ "inverse-success": "#69dca1",
+ "inverse-warning": "#fdbb28",
+ "inverse-info": "#aec6ff"
+ },
+ "dark": {
+ "background": "#0f0d12",
+ "on-background": "#eae3ef",
+ "surface": "#0f0d12",
+ "surface-dim": "#0f0d12",
+ "surface-bright": "#2e2b34",
+ "surface-container-lowest": "#000000",
+ "surface-container-low": "#141218",
+ "surface-container": "#1b181f",
+ "surface-container-high": "#211e26",
+ "surface-container-highest": "#27242d",
+ "on-surface": "#eae3ef",
+ "on-surface-variant": "#aea9b4",
+ "outline": "#78737e",
+ "outline-variant": "#4a4650",
+ "inverse-surface": "#fdf7fe",
+ "inverse-on-surface": "#575459",
+ "primary": "#cdc0ec",
+ "primary-dim": "#bfb2de",
+ "on-primary": "#443a5f",
+ "primary-container": "#574d72",
+ "on-primary-container": "#e9deff",
+ "primary-fixed": "#ded0fe",
+ "primary-fixed-dim": "#d0c3ef",
+ "on-primary-fixed": "#3c3256",
+ "on-primary-fixed-variant": "#594e74",
+ "inverse-primary": "#645980",
+ "secondary": "#cbc2db",
+ "secondary-dim": "#beb5cd",
+ "on-secondary": "#433d51",
+ "secondary-container": "#3e384c",
+ "on-secondary-container": "#c4bbd4",
+ "secondary-fixed": "#e8def8",
+ "secondary-fixed-dim": "#dad0ea",
+ "on-secondary-fixed": "#423c50",
+ "on-secondary-fixed-variant": "#5f586e",
+ "tertiary": "#ffcfef",
+ "tertiary-dim": "#f4bfe3",
+ "on-tertiary": "#69415f",
+ "tertiary-container": "#f4bfe3",
+ "on-tertiary-container": "#5f3956",
+ "tertiary-fixed": "#f4bfe3",
+ "tertiary-fixed-dim": "#e5b2d5",
+ "on-tertiary-fixed": "#4a2642",
+ "on-tertiary-fixed-variant": "#694260",
+ "error": "#f97386",
+ "error-dim": "#c44b5f",
+ "on-error": "#490013",
+ "error-container": "#871c34",
+ "on-error-container": "#ff97a3",
+ "success": "#69dca1",
+ "on-success": "#003822",
+ "success-container": "#005233",
+ "on-success-container": "#86f9bc",
+ "warning": "#fdbb28",
+ "on-warning": "#412d00",
+ "warning-container": "#5e4200",
+ "on-warning-container": "#ffdea6",
+ "info": "#aec6ff",
+ "on-info": "#002e6a",
+ "info-container": "#004396",
+ "on-info-container": "#d8e2ff",
+ "inverse-error": "#a8364b",
+ "inverse-success": "#006c45",
+ "inverse-warning": "#7c5800",
+ "inverse-info": "#005ac4"
+ },
+ "default": "baseline",
+ "profiles": {
+ "baseline": {
+ "label": "Baseline",
+ "seed": "#6750a4",
+ "variant": "tonal-spot",
+ "spec": "2025",
+ "contrast": 0,
+ "light": {
+ "background": "#fdf7fe",
+ "on-background": "#34313a",
+ "surface": "#fdf7fe",
+ "surface-dim": "#ded8e4",
+ "surface-bright": "#fdf7fe",
+ "surface-container-lowest": "#ffffff",
+ "surface-container-low": "#f8f1fa",
+ "surface-container": "#f2ecf5",
+ "surface-container-high": "#ece6f0",
+ "surface-container-highest": "#e7e0ec",
+ "on-surface": "#34313a",
+ "on-surface-variant": "#615d68",
+ "outline": "#7d7983",
+ "outline-variant": "#b5b0bb",
+ "inverse-surface": "#0f0d12",
+ "inverse-on-surface": "#a09ba1",
+ "primary": "#655789",
+ "primary-dim": "#594b7c",
+ "on-primary": "#fdf7ff",
+ "primary-container": "#d4c3fd",
+ "on-primary-container": "#493c6c",
+ "primary-fixed": "#d4c3fd",
+ "primary-fixed-dim": "#c6b6ee",
+ "on-primary-fixed": "#352857",
+ "on-primary-fixed-variant": "#524576",
+ "inverse-primary": "#d4c3fd",
+ "secondary": "#625c71",
+ "secondary-dim": "#565065",
+ "on-secondary": "#fdf7ff",
+ "secondary-container": "#e8def8",
+ "on-secondary-container": "#554f63",
+ "secondary-fixed": "#e8def8",
+ "secondary-fixed-dim": "#dad0ea",
+ "on-secondary-fixed": "#423c50",
+ "on-secondary-fixed-variant": "#5f586e",
+ "tertiary": "#7b5270",
+ "tertiary-dim": "#6e4664",
+ "on-tertiary": "#fff7f9",
+ "tertiary-container": "#f4bfe3",
+ "on-tertiary-container": "#5f3956",
+ "tertiary-fixed": "#f4bfe3",
+ "tertiary-fixed-dim": "#e5b2d5",
+ "on-tertiary-fixed": "#4a2642",
+ "on-tertiary-fixed-variant": "#694260",
+ "error": "#a8364b",
+ "error-dim": "#6b0221",
+ "on-error": "#fff7f7",
+ "error-container": "#f97386",
+ "on-error-container": "#6e0523",
+ "success": "#006c45",
+ "on-success": "#ffffff",
+ "success-container": "#86f9bc",
+ "on-success-container": "#002112",
+ "warning": "#7c5800",
+ "on-warning": "#ffffff",
+ "warning-container": "#ffdea6",
+ "on-warning-container": "#271900",
+ "info": "#005ac4",
+ "on-info": "#ffffff",
+ "info-container": "#d8e2ff",
+ "on-info-container": "#001a42",
+ "inverse-error": "#f97386",
+ "inverse-success": "#69dca1",
+ "inverse-warning": "#fdbb28",
+ "inverse-info": "#aec6ff"
+ },
+ "dark": {
+ "background": "#0f0d12",
+ "on-background": "#eae3ef",
+ "surface": "#0f0d12",
+ "surface-dim": "#0f0d12",
+ "surface-bright": "#2e2b34",
+ "surface-container-lowest": "#000000",
+ "surface-container-low": "#141218",
+ "surface-container": "#1b181f",
+ "surface-container-high": "#211e26",
+ "surface-container-highest": "#27242d",
+ "on-surface": "#eae3ef",
+ "on-surface-variant": "#aea9b4",
+ "outline": "#78737e",
+ "outline-variant": "#4a4650",
+ "inverse-surface": "#fdf7fe",
+ "inverse-on-surface": "#575459",
+ "primary": "#cdc0ec",
+ "primary-dim": "#bfb2de",
+ "on-primary": "#443a5f",
+ "primary-container": "#574d72",
+ "on-primary-container": "#e9deff",
+ "primary-fixed": "#ded0fe",
+ "primary-fixed-dim": "#d0c3ef",
+ "on-primary-fixed": "#3c3256",
+ "on-primary-fixed-variant": "#594e74",
+ "inverse-primary": "#645980",
+ "secondary": "#cbc2db",
+ "secondary-dim": "#beb5cd",
+ "on-secondary": "#433d51",
+ "secondary-container": "#3e384c",
+ "on-secondary-container": "#c4bbd4",
+ "secondary-fixed": "#e8def8",
+ "secondary-fixed-dim": "#dad0ea",
+ "on-secondary-fixed": "#423c50",
+ "on-secondary-fixed-variant": "#5f586e",
+ "tertiary": "#ffcfef",
+ "tertiary-dim": "#f4bfe3",
+ "on-tertiary": "#69415f",
+ "tertiary-container": "#f4bfe3",
+ "on-tertiary-container": "#5f3956",
+ "tertiary-fixed": "#f4bfe3",
+ "tertiary-fixed-dim": "#e5b2d5",
+ "on-tertiary-fixed": "#4a2642",
+ "on-tertiary-fixed-variant": "#694260",
+ "error": "#f97386",
+ "error-dim": "#c44b5f",
+ "on-error": "#490013",
+ "error-container": "#871c34",
+ "on-error-container": "#ff97a3",
+ "success": "#69dca1",
+ "on-success": "#003822",
+ "success-container": "#005233",
+ "on-success-container": "#86f9bc",
+ "warning": "#fdbb28",
+ "on-warning": "#412d00",
+ "warning-container": "#5e4200",
+ "on-warning-container": "#ffdea6",
+ "info": "#aec6ff",
+ "on-info": "#002e6a",
+ "info-container": "#004396",
+ "on-info-container": "#d8e2ff",
+ "inverse-error": "#a8364b",
+ "inverse-success": "#006c45",
+ "inverse-warning": "#7c5800",
+ "inverse-info": "#005ac4"
+ }
+ },
+ "teal": {
+ "label": "Teal",
+ "seed": "#00897b",
+ "variant": "vibrant",
+ "spec": "2025",
+ "contrast": 0,
+ "light": {
+ "background": "#d3fffd",
+ "on-background": "#003534",
+ "surface": "#d3fffd",
+ "surface-dim": "#87e4e1",
+ "surface-bright": "#d3fffd",
+ "surface-container-lowest": "#ffffff",
+ "surface-container-low": "#bafdfa",
+ "surface-container": "#adf5f2",
+ "surface-container-high": "#a2f0ed",
+ "surface-container-highest": "#96ece8",
+ "on-surface": "#003534",
+ "on-surface-variant": "#296463",
+ "outline": "#46807e",
+ "outline-variant": "#7db7b5",
+ "inverse-surface": "#001111",
+ "inverse-on-surface": "#6da7a5",
+ "primary": "#00675c",
+ "primary-dim": "#005a50",
+ "on-primary": "#c0fff3",
+ "primary-container": "#00f7df",
+ "on-primary-container": "#00594f",
+ "primary-fixed": "#00f7df",
+ "primary-fixed-dim": "#00e8d1",
+ "on-primary-fixed": "#00443c",
+ "on-primary-fixed-variant": "#006359",
+ "inverse-primary": "#00fee5",
+ "secondary": "#006765",
+ "secondary-dim": "#005958",
+ "on-secondary": "#bcfffc",
+ "secondary-container": "#38fbf7",
+ "on-secondary-container": "#005c5a",
+ "secondary-fixed": "#38fbf7",
+ "secondary-fixed-dim": "#10ece8",
+ "on-secondary-fixed": "#004746",
+ "on-secondary-fixed-variant": "#006765",
+ "tertiary": "#006386",
+ "tertiary-dim": "#005675",
+ "on-tertiary": "#e7f5ff",
+ "tertiary-container": "#20c0ff",
+ "on-tertiary-container": "#00374d",
+ "tertiary-fixed": "#20c0ff",
+ "tertiary-fixed-dim": "#00b2ee",
+ "on-tertiary-fixed": "#001e2b",
+ "on-tertiary-fixed-variant": "#004059",
+ "error": "#b31b25",
+ "error-dim": "#9f0519",
+ "on-error": "#ffefee",
+ "error-container": "#fb5151",
+ "on-error-container": "#570008",
+ "success": "#006c45",
+ "on-success": "#ffffff",
+ "success-container": "#86f9bc",
+ "on-success-container": "#002112",
+ "warning": "#7c5800",
+ "on-warning": "#ffffff",
+ "warning-container": "#ffdea6",
+ "on-warning-container": "#271900",
+ "info": "#005ac4",
+ "on-info": "#ffffff",
+ "info-container": "#d8e2ff",
+ "on-info-container": "#001a42",
+ "inverse-error": "#ff716c",
+ "inverse-success": "#69dca1",
+ "inverse-warning": "#fdbb28",
+ "inverse-info": "#aec6ff"
+ },
+ "dark": {
+ "background": "#001111",
+ "on-background": "#b8f3f1",
+ "surface": "#001111",
+ "surface-dim": "#001111",
+ "surface-bright": "#003231",
+ "surface-container-lowest": "#000000",
+ "surface-container-low": "#001716",
+ "surface-container": "#001e1d",
+ "surface-container-high": "#002424",
+ "surface-container-highest": "#002b2a",
+ "on-surface": "#b8f3f1",
+ "on-surface-variant": "#7bb5b3",
+ "outline": "#457f7d",
+ "outline-variant": "#0e504f",
+ "inverse-surface": "#e3fffd",
+ "inverse-on-surface": "#215e5c",
+ "primary": "#b4fff1",
+ "primary-dim": "#00fee5",
+ "on-primary": "#00665b",
+ "primary-container": "#00fee5",
+ "on-primary-container": "#005c53",
+ "primary-fixed": "#00f7df",
+ "primary-fixed-dim": "#00e8d1",
+ "on-primary-fixed": "#00443c",
+ "on-primary-fixed-variant": "#006359",
+ "inverse-primary": "#006b60",
+ "secondary": "#38fbf7",
+ "secondary-dim": "#10ece8",
+ "on-secondary": "#005c5a",
+ "secondary-container": "#006a68",
+ "on-secondary-container": "#dafffd",
+ "secondary-fixed": "#38fbf7",
+ "secondary-fixed-dim": "#10ece8",
+ "on-secondary-fixed": "#004746",
+ "on-secondary-fixed-variant": "#006765",
+ "tertiary": "#68ccff",
+ "tertiary-dim": "#20c0ff",
+ "on-tertiary": "#00415a",
+ "tertiary-container": "#20c0ff",
+ "on-tertiary-container": "#00374d",
+ "tertiary-fixed": "#20c0ff",
+ "tertiary-fixed-dim": "#00b2ee",
+ "on-tertiary-fixed": "#001e2b",
+ "on-tertiary-fixed-variant": "#004059",
+ "error": "#ff716c",
+ "error-dim": "#d7383b",
+ "on-error": "#490006",
+ "error-container": "#9f0519",
+ "on-error-container": "#ffa8a3",
+ "success": "#69dca1",
+ "on-success": "#003822",
+ "success-container": "#005233",
+ "on-success-container": "#86f9bc",
+ "warning": "#fdbb28",
+ "on-warning": "#412d00",
+ "warning-container": "#5e4200",
+ "on-warning-container": "#ffdea6",
+ "info": "#aec6ff",
+ "on-info": "#002e6a",
+ "info-container": "#004396",
+ "on-info-container": "#d8e2ff",
+ "inverse-error": "#b31b25",
+ "inverse-success": "#006c45",
+ "inverse-warning": "#7c5800",
+ "inverse-info": "#005ac4"
+ }
+ },
+ "rose": {
+ "label": "Rose",
+ "seed": "#c2185b",
+ "variant": "vibrant",
+ "spec": "2025",
+ "contrast": 0,
+ "light": {
+ "background": "#fff4f6",
+ "on-background": "#492136",
+ "surface": "#fff4f6",
+ "surface-dim": "#ffc4de",
+ "surface-bright": "#fff4f6",
+ "surface-container-lowest": "#ffffff",
+ "surface-container-low": "#ffecf2",
+ "surface-container": "#ffe0ec",
+ "surface-container-high": "#ffd8e8",
+ "surface-container-highest": "#ffd0e4",
+ "on-surface": "#492136",
+ "on-surface-variant": "#7c4d64",
+ "outline": "#9b6880",
+ "outline-variant": "#d69db7",
+ "inverse-surface": "#220215",
+ "inverse-on-surface": "#c58da6",
+ "primary": "#ae1d53",
+ "primary-dim": "#9e0b47",
+ "on-primary": "#ffeff0",
+ "primary-container": "#ff7198",
+ "on-primary-container": "#4d001e",
+ "primary-fixed": "#ff7198",
+ "primary-fixed-dim": "#f95a8a",
+ "on-primary-fixed": "#000000",
+ "on-primary-fixed-variant": "#5e0027",
+ "inverse-primary": "#f65787",
+ "secondary": "#99366e",
+ "secondary-dim": "#8a2a62",
+ "on-secondary": "#ffeff3",
+ "secondary-container": "#ffc0dc",
+ "on-secondary-container": "#7f2159",
+ "secondary-fixed": "#ffc0dc",
+ "secondary-fixed-dim": "#ffabd2",
+ "on-secondary-fixed": "#670845",
+ "on-secondary-fixed-variant": "#8b2b63",
+ "tertiary": "#5c4bb4",
+ "tertiary-dim": "#503ea7",
+ "on-tertiary": "#f6f0ff",
+ "tertiary-container": "#b3a5ff",
+ "on-tertiary-container": "#301887",
+ "tertiary-fixed": "#b3a5ff",
+ "tertiary-fixed-dim": "#a595ff",
+ "on-tertiary-fixed": "#170059",
+ "on-tertiary-fixed-variant": "#39248f",
+ "error": "#b31b25",
+ "error-dim": "#9f0519",
+ "on-error": "#ffefee",
+ "error-container": "#fb5151",
+ "on-error-container": "#570008",
+ "success": "#006c45",
+ "on-success": "#ffffff",
+ "success-container": "#86f9bc",
+ "on-success-container": "#002112",
+ "warning": "#7c5800",
+ "on-warning": "#ffffff",
+ "warning-container": "#ffdea6",
+ "on-warning-container": "#271900",
+ "info": "#005ac4",
+ "on-info": "#ffffff",
+ "info-container": "#d8e2ff",
+ "on-info-container": "#001a42",
+ "inverse-error": "#ff716c",
+ "inverse-success": "#69dca1",
+ "inverse-warning": "#fdbb28",
+ "inverse-info": "#aec6ff"
+ },
+ "dark": {
+ "background": "#220215",
+ "on-background": "#ffdcea",
+ "surface": "#220215",
+ "surface-dim": "#220215",
+ "surface-bright": "#4e1737",
+ "surface-container-lowest": "#000000",
+ "surface-container-low": "#2a041b",
+ "surface-container": "#330822",
+ "surface-container-high": "#3c0d28",
+ "surface-container-highest": "#45122f",
+ "on-surface": "#ffdcea",
+ "on-surface-variant": "#d49bb4",
+ "outline": "#99667e",
+ "outline-variant": "#663a50",
+ "inverse-surface": "#fff8f8",
+ "inverse-on-surface": "#75475d",
+ "primary": "#ff8aa7",
+ "primary-dim": "#ff6b95",
+ "on-primary": "#620029",
+ "primary-container": "#ff7198",
+ "on-primary-container": "#4d001e",
+ "primary-fixed": "#ff7198",
+ "primary-fixed-dim": "#f95a8a",
+ "on-primary-fixed": "#000000",
+ "on-primary-fixed-variant": "#5e0027",
+ "inverse-primary": "#b32257",
+ "secondary": "#ff8ac6",
+ "secondary-dim": "#ef7db9",
+ "on-secondary": "#620241",
+ "secondary-container": "#7f2058",
+ "on-secondary-container": "#ffbedb",
+ "secondary-fixed": "#ffc0dc",
+ "secondary-fixed-dim": "#ffabd2",
+ "on-secondary-fixed": "#670845",
+ "on-secondary-fixed-variant": "#8b2b63",
+ "tertiary": "#aa9bff",
+ "tertiary-dim": "#9f8ffc",
+ "on-tertiary": "#290c80",
+ "tertiary-container": "#9d8cfa",
+ "on-tertiary-container": "#1c0068",
+ "tertiary-fixed": "#b3a5ff",
+ "tertiary-fixed-dim": "#a595ff",
+ "on-tertiary-fixed": "#170059",
+ "on-tertiary-fixed-variant": "#39248f",
+ "error": "#ff716c",
+ "error-dim": "#d7383b",
+ "on-error": "#490006",
+ "error-container": "#9f0519",
+ "on-error-container": "#ffa8a3",
+ "success": "#69dca1",
+ "on-success": "#003822",
+ "success-container": "#005233",
+ "on-success-container": "#86f9bc",
+ "warning": "#fdbb28",
+ "on-warning": "#412d00",
+ "warning-container": "#5e4200",
+ "on-warning-container": "#ffdea6",
+ "info": "#aec6ff",
+ "on-info": "#002e6a",
+ "info-container": "#004396",
+ "on-info-container": "#d8e2ff",
+ "inverse-error": "#b31b25",
+ "inverse-success": "#006c45",
+ "inverse-warning": "#7c5800",
+ "inverse-info": "#005ac4"
+ }
+ }
+ }
+}