diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index fbd75059..c497c8ec 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -170,7 +170,7 @@ jobs: # entirely on the version openregister cannot load. # # EVERY MAJOR IN THE DECLARED RANGE, NOT JUST ITS ENDS. appinfo/info.xml declares - # , and + # , and # tests/Unit/ClaimAccuracyTest::testDeclaredNextcloudRangeMatchesTheTestedMatrix # asserts that the lowest ref here EQUALS that floor. A matrix of stable34 # alone therefore advertises 32 and 33 to the app store while exercising @@ -187,14 +187,20 @@ jobs: # in the LOOSER direction, which this fleet does not take. The floor is still # 32, so ClaimAccuracyTest's lowest-ref-equals-floor assertion is unaffected. # - # stable34 is FIRST deliberately. Playwright is the job that reads [0], and + # stable35 is FIRST deliberately, and moved there when max-version rose to + # 35. Playwright is the job that reads [0], and # tests/e2e/spec-coverage/selector-liveness.spec.ts is a cross-version # stylesheet audit whose SINCE deferrals expire only once the survey - # reaches the newest supported major (MAX_SUPPORTED_NC). Surveying 32 would - # defer the NC33+ header selectors indefinitely; surveying 34 forces them - # to be proven live — which is how the eight dead NC 32/33-only selectors - # now recorded in that file's ALLOWED list were found. - nextcloud-test-refs: '["stable34", "stable32", "stable33"]' + # reaches the newest supported major (MAX_SUPPORTED_NC, a constant in that + # spec file that mirrors info.xml BY HAND — it still reads 34, so raising + # it is a separate decision: it expires that file's SINCE deferrals). + # Surveying 32 would defer the NC33+ header selectors + # indefinitely; surveying the newest major forces them to be proven live — + # which is how the eight dead NC 32/33-only selectors now recorded in that + # file's ALLOWED list were found. Leaving stable34 in front of a declared + # 35 would put the survey one major behind the claim and start that drift + # over again. + nextcloud-test-refs: '["stable35", "stable32", "stable33", "stable34"]' # These three were `false`, and that is worse than it sounds. The shared # workflow builds one matrix leg per tool unconditionally; a disabled leg # prints " is disabled — skipping." and `exit 0`. The job therefore diff --git a/.gitignore b/.gitignore index d6ec7ae8..4e64d4a6 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,17 @@ # boot, so it must never be committed (a stray test value here restyles the # whole instance the moment the app is activated). /css/custom-overrides.css +# The same, per set, for every set on no design system: the stock Nextcloud +# theme (custom-overrides-nextcloud.css) and the themes saved off it. Only that +# set loads its file, so emptying the stock one (the reset) gives stock back. +/css/custom-overrides-*.css + +# Runtime theme state — Nextcloud's own logos, favicon and background image, +# copied into a theme when it is saved from the editor (BrandingCaptureService) +# so applying the theme brings them back. They are an admin's uploads on one +# instance, never part of the app. +/img/logos/*-captured-* +/img/backgrounds/*-captured-* # Runtime theme state — written by the freeform custom-CSS editor at runtime # (CustomCssService::write(), atomic temp-file + rename). Unlike diff --git a/.prettierignore b/.prettierignore index 843e68f5..c2901175 100644 --- a/.prettierignore +++ b/.prettierignore @@ -52,3 +52,10 @@ scripts/sources/ # file — so the validation tests have something to reject. Formatting them # would remove the very defect under test. tests/integration/fixtures/ + +# Same contract again: both emitted by scripts/generate-component-scopes.mjs +# from scripts/mapping/component-tokens.json and byte-compared against the +# committed copies by `npm run test:component-scopes`. Prettier would rewrap +# the var() fallback chains and fail that check on a file nobody edited. +css/component-scopes.css +css/primary-lock.css diff --git a/appinfo/routes.php b/appinfo/routes.php index d031390f..c257004d 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -32,6 +32,8 @@ ['name' => 'settings#getTokenSet', 'url' => '/settings/tokenset', 'verb' => 'GET'], ['name' => 'settings#setSloganSetting', 'url' => '/settings/slogan', 'verb' => 'POST'], ['name' => 'settings#setMenuLabelsSetting', 'url' => '/settings/menulabels', 'verb' => 'POST'], + ['name' => 'settings#setPrimaryDrivesComponentsSetting', 'url' => '/settings/primary-drives-components', 'verb' => 'POST'], + ['name' => 'settings#setSaveConfirmSettings', 'url' => '/settings/save-confirmations', 'verb' => 'POST'], ['name' => 'settings#getThemingValues', 'url' => '/settings/theming', 'verb' => 'GET'], ['name' => 'settings#updateThemingValues', 'url' => '/settings/theming', 'verb' => 'POST'], // Resetting core theming to stock deliberately has NO route of its own: diff --git a/composer.json b/composer.json index 4bfa7f6c..888fdcc8 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "conduction/hydra-gates": "^1.8.2", "cyclonedx/cyclonedx-php-composer": "^6.2", "edgedesign/phpqa": "^1.27", - "nextcloud/ocp": "^34.0", + "nextcloud/ocp": "^35.0", "phpcsstandards/phpcsextra": "^1.4", "phpmd/phpmd": "^2.15", "phpmetrics/phpmetrics": "^2.8", diff --git a/composer.lock b/composer.lock index 5d8d2af6..1e5e4dcf 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "09674095784df6bd8ef3f0c7c8c2573c", + "content-hash": "11071185487184c005334265eef94abf", "packages": [], "packages-dev": [ { @@ -1929,30 +1929,34 @@ }, { "name": "nextcloud/ocp", - "version": "v34.0.4", + "version": "v35.0.0", "source": { "type": "git", "url": "https://github.com/nextcloud-deps/ocp.git", - "reference": "f316bffe9b506532d1aafcf817d8d17f3bbe2cde" + "reference": "94d85a0ba6b3b3911c25b2eddb0ca0fe5691acbf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nextcloud-deps/ocp/zipball/f316bffe9b506532d1aafcf817d8d17f3bbe2cde", - "reference": "f316bffe9b506532d1aafcf817d8d17f3bbe2cde", + "url": "https://api.github.com/repos/nextcloud-deps/ocp/zipball/94d85a0ba6b3b3911c25b2eddb0ca0fe5691acbf", + "reference": "94d85a0ba6b3b3911c25b2eddb0ca0fe5691acbf", "shasum": "" }, "require": { - "php": "~8.2 || ~8.3 || ~8.4 || ~8.5", + "php": "~8.3 || ~8.4 || ~8.5", "psr/clock": "^1.0", "psr/container": "^2.0.2", "psr/event-dispatcher": "^1.0", "psr/http-client": "^1.0.3", - "psr/log": "^3.0.2" + "psr/log": "^3.0.2", + "symfony/polyfill-intl-normalizer": "^1.38", + "symfony/polyfill-php84": "^1.38", + "symfony/polyfill-php85": "^1.41", + "symfony/polyfill-php86": "^1.41" }, "type": "library", "extra": { "branch-alias": { - "dev-stable34": "34.0.0-dev" + "dev-stable35": "35.0.0-dev" } }, "notification-url": "https://packagist.org/downloads/", @@ -1972,9 +1976,9 @@ "description": "Composer package containing Nextcloud's public OCP API and the unstable NCU API", "support": { "issues": "https://github.com/nextcloud-deps/ocp/issues", - "source": "https://github.com/nextcloud-deps/ocp/tree/v34.0.4" + "source": "https://github.com/nextcloud-deps/ocp/tree/v35.0.0" }, - "time": "2026-09-02T01:51:50+00:00" + "time": "2026-09-04T01:52:36+00:00" }, { "name": "nikic/php-parser", @@ -7055,16 +7059,16 @@ }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.38.0", + "version": "v1.42.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b" + "reference": "aa20edea75bd9c48cfecc8360922e5a6e5c44502" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/2d446c214bdbe5b71bde5011b060a05fece3ae6b", - "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/aa20edea75bd9c48cfecc8360922e5a6e5c44502", + "reference": "aa20edea75bd9c48cfecc8360922e5a6e5c44502", "shasum": "" }, "require": { @@ -7116,7 +7120,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.38.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.42.0" }, "funding": [ { @@ -7136,7 +7140,7 @@ "type": "tidelift" } ], - "time": "2026-05-25T13:48:31+00:00" + "time": "2026-08-07T06:33:24+00:00" }, { "name": "symfony/polyfill-mbstring", @@ -7303,6 +7307,246 @@ ], "time": "2026-05-26T12:45:58+00:00" }, + { + "name": "symfony/polyfill-php84", + "version": "v1.38.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", + "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php84\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php84/tree/v1.38.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-26T12:51:13+00:00" + }, + { + "name": "symfony/polyfill-php85", + "version": "v1.41.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php85.git", + "reference": "255fab485aaa1006ed411040c42aecd7b5302d7a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/255fab485aaa1006ed411040c42aecd7b5302d7a", + "reference": "255fab485aaa1006ed411040c42aecd7b5302d7a", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php85\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php85/tree/v1.41.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-07-01T12:47:55+00:00" + }, + { + "name": "symfony/polyfill-php86", + "version": "v1.41.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php86.git", + "reference": "6bc356ed3d8dbfeea8f0de235e34d670704e880e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php86/zipball/6bc356ed3d8dbfeea8f0de235e34d670704e880e", + "reference": "6bc356ed3d8dbfeea8f0de235e34d670704e880e", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php86\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.6+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php86/tree/v1.41.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-07-02T13:42:24+00:00" + }, { "name": "symfony/process", "version": "v6.4.41", @@ -7755,16 +7999,16 @@ }, { "name": "twig/twig", - "version": "v3.28.0", + "version": "v3.29.0", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "597c12ed286fb9d1701a36684ce6e0cbe28ebc8b" + "reference": "45a3c6e9224c3377a39c7b150bb29d5d97d2c75d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/597c12ed286fb9d1701a36684ce6e0cbe28ebc8b", - "reference": "597c12ed286fb9d1701a36684ce6e0cbe28ebc8b", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/45a3c6e9224c3377a39c7b150bb29d5d97d2c75d", + "reference": "45a3c6e9224c3377a39c7b150bb29d5d97d2c75d", "shasum": "" }, "require": { @@ -7819,7 +8063,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.28.0" + "source": "https://github.com/twigphp/Twig/tree/v3.29.0" }, "funding": [ { @@ -7831,7 +8075,7 @@ "type": "tidelift" } ], - "time": "2026-07-03T20:44:34+00:00" + "time": "2026-09-18T09:10:14+00:00" }, { "name": "vimeo/psalm", diff --git a/css/admin.css b/css/admin.css index 393930de..f63daa78 100644 --- a/css/admin.css +++ b/css/admin.css @@ -555,6 +555,17 @@ padding: 7px 0; border-bottom: 1px solid var(--color-border); } +/* A row the "primary drives every component" setting has taken ownership of. + * + * Dimmed rather than hidden: the stored value is still there and comes back the + * moment the setting is switched off, and a row that vanished would read as a + * value that was thrown away. `cursor: not-allowed` is on the row rather than + * the input because a disabled input does not fire pointer events, so the + * cursor would never change over the control itself. */ +.nldesign-token-row--locked { + opacity: 0.55; + cursor: not-allowed; +} .nldesign-token-label-wrap { flex: 1 1 auto; min-width: 0; @@ -758,14 +769,19 @@ align-items: center; gap: 10px; padding: 0 10px; - background: var(--prev-header-bg); + /* The header reads its own component tokens first, the way #header does in + systems/nldesign/theme.css, and the set summary only when they are unset. */ + background: var( + --nldesign-component-header-background-color, + var(--prev-header-bg) + ); border-bottom: var(--prev-header-border); } .nl-mini__logo { width: 22px; height: 11px; border-radius: 2px; - background: var(--prev-header-text); + background: var(--nldesign-component-header-color, var(--prev-header-text)); opacity: 0.9; flex: none; } @@ -774,7 +790,7 @@ width: 24px; height: 6px; border-radius: 3px; - background: var(--prev-header-text); + background: var(--nldesign-component-header-color, var(--prev-header-text)); opacity: 0.45; flex: none; } @@ -791,7 +807,7 @@ bottom: -9px; height: 3px; border-radius: 2px; - background: var(--prev-header-text); + background: var(--nldesign-component-header-color, var(--prev-header-text)); } .nl-mini__navspacer { flex: 1; @@ -800,15 +816,16 @@ width: 9px; height: 9px; border-radius: 50%; - background: var(--prev-header-text); + background: var(--nldesign-component-header-color, var(--prev-header-text)); opacity: 0.7; flex: none; } .nl-mini__avatar { + position: relative; width: 14px; height: 14px; border-radius: 50%; - background: var(--prev-header-text); + background: var(--nldesign-component-header-color, var(--prev-header-text)); opacity: 0.9; flex: none; } @@ -819,7 +836,10 @@ display: flex; min-height: 0; margin: 5px; - border-radius: var(--prev-radius-container); + border-radius: var( + --nldesign-component-content-card-container-border-radius, + var(--prev-radius-container) + ); overflow: hidden; background: var(--prev-surface); } @@ -849,9 +869,18 @@ align-items: center; gap: 5px; font-size: 8px; - color: var(--prev-text); + /* Both, because the entry is a `span`: nldesign's blanket text rule claims + every span with `!important` and reads this property first. */ + --nldesign-color-on-surface: var( + --nldesign-component-navigation-color, + var(--prev-text) + ); + color: var(--nldesign-component-navigation-color, var(--prev-text)); padding: 4px 7px; - border-radius: var(--prev-radius-pill); + border-radius: var( + --nldesign-component-navigation-border-radius, + var(--prev-radius-pill) + ); white-space: nowrap; overflow: hidden; } @@ -864,7 +893,11 @@ flex: none; } .nl-mini__menuitem--active { - background: var(--prev-primary); + background: var( + --nldesign-component-navigation-active-background-color, + var(--prev-primary) + ); + --nldesign-color-on-surface: var(--prev-primary-text); color: var(--prev-primary-text); font-weight: 700; } @@ -879,7 +912,8 @@ } .nl-mini__widget { background: var(--prev-surface); - border: 1px solid var(--prev-border); + border: 1px solid + var(--nldesign-component-content-card-border-color, var(--prev-border)); border-radius: var(--prev-radius); overflow: hidden; height: 100%; @@ -897,12 +931,46 @@ .nl-mini__table th { height: 5px; } +/* The header row: its fill, label colour and label weight, each from its own + table token. The labels are words rather than bars so the weight shows. */ .nl-mini__table thead th { - border-bottom: 1px solid var(--prev-border); + padding: 4px 9px; + border-bottom: 1px solid + var( + --nldesign-component-table-border-color, + var(--color-border, var(--prev-border)) + ); + background-color: var( + --nldesign-component-table-header-background-color, + transparent + ); + color: var( + --nldesign-component-table-header-color, + var(--color-text-maxcontrast, var(--prev-muted)) + ); + font-size: 7px; + font-weight: var(--nldesign-component-table-header-font-weight, normal); + text-align: left; } .nl-mini__table td { padding: 6px 9px; - border-bottom: 1px solid var(--prev-border); + border-bottom: 1px solid + var( + --nldesign-component-table-border-color, + var(--color-border, var(--prev-border)) + ); +} +.nl-mini__table tr.is-zebra td { + background: var( + --nldesign-component-table-zebra-background-color, + var(--color-background-dark, var(--prev-bg)) + ); +} +.nl-mini__table tr.is-hover td { + background: var( + --nldesign-component-table-row-hover-background-color, + var(--color-background-hover, var(--prev-bg)) + ); } .nl-mini__table td::after { content: ''; @@ -937,7 +1005,8 @@ .nl-mini__sidebar { flex: 0 0 88px; background: var(--prev-surface); - border-left: 1px solid var(--prev-border); + border-left: 1px solid + var(--nldesign-component-sidebar-border-color, var(--prev-border)); padding: 8px; display: flex; flex-direction: column; @@ -960,6 +1029,34 @@ border: 1px solid var(--prev-border); flex: none; } +/* The sidebar's tab strip: the rule under it is the divider, the open tab's + underline the sidebar's active colour. */ +.nl-mini__sidebar-tabs { + display: flex; + gap: 6px; + border-bottom: 1px solid + var(--nldesign-component-sidebar-divider-color, var(--prev-border)); +} +.nl-mini__sidebar-tab { + width: 20px; + height: 4px; + margin-bottom: 3px; + border-radius: 2px; + background: var(--prev-border); +} +.nl-mini__sidebar-tab--active { + position: relative; + background: var(--prev-muted); +} +.nl-mini__sidebar-tab--active::after { + content: ''; + position: absolute; + left: 0; + right: 0; + bottom: -4px; + height: 2px; + background: var(--nldesign-component-sidebar-active-color, var(--prev-primary)); +} .nl-mini__line { display: block; height: 5px; @@ -970,6 +1067,471 @@ width: 60%; } +/* The status components in miniature, each painted from its own component + tokens and falling back to the Nextcloud colour the component reads when it + has none. Text in them is a `span`, so each also sets the on-surface + property nldesign's blanket text rule reads first. */ + +/* Badge: a counter beside the dialog title, and the favourite star. */ +.nl-mini__counter { + display: inline-block; + margin-left: 5px; + vertical-align: middle; + min-width: 12px; + padding: 0 3px; + border-radius: var(--nldesign-component-badge-border-radius, 999px); + background: var( + --nldesign-component-badge-background-color, + var(--prev-primary) + ); + --nldesign-color-on-surface: var( + --nldesign-component-badge-color, + var(--prev-primary-text) + ); + color: var(--nldesign-component-badge-color, var(--prev-primary-text)); + font-size: 7px; + font-weight: 700; + line-height: 10px; + text-align: center; +} +.nl-mini__star { + margin-left: 4px; + --nldesign-color-on-surface: var( + --nldesign-component-badge-favorite-color, + var(--color-favorite, #a08b00) + ); + color: var( + --nldesign-component-badge-favorite-color, + var(--color-favorite, #a08b00) + ); +} + +/* Note cards: the type colour is the fill, and the type text colour the + stripe and the glyph, as NcNoteCard draws them from Nextcloud 33. */ +.nl-mini__notes { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 5px; + margin-top: 2px; +} +.nl-mini__note { + display: flex; + align-items: center; + gap: 4px; + padding: 4px 5px; + border-inline-start: 2px solid var(--nl-note-icon); + border-radius: var( + --nldesign-component-notecard-border-radius, + var(--border-radius, 3px) + ); + background: var(--nl-note-fill); +} +.nl-mini__note-icon { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--nl-note-icon); + flex: none; +} +.nl-mini__note-text { + flex: 1; + height: 3px; + border-radius: 2px; + background: var(--nl-note-text); + opacity: 0.6; +} +.nl-mini__note--info { + --nl-note-fill: var(--nldesign-component-notecard-info-color, var(--color-info)); + --nl-note-icon: var( + --nldesign-component-notecard-info-icon-color, + var(--color-info-text, var(--color-element-info)) + ); + --nl-note-text: var( + --nldesign-component-notecard-info-text-color, + var(--prev-text) + ); +} +.nl-mini__note--warning { + --nl-note-fill: var( + --nldesign-component-notecard-warning-color, + var(--color-warning) + ); + --nl-note-icon: var( + --nldesign-component-notecard-warning-icon-color, + var(--color-warning-text, var(--color-element-warning)) + ); + --nl-note-text: var( + --nldesign-component-notecard-warning-text-color, + var(--prev-text) + ); +} +.nl-mini__note--error { + --nl-note-fill: var( + --nldesign-component-notecard-error-color, + var(--color-error) + ); + --nl-note-icon: var( + --nldesign-component-notecard-error-icon-color, + var(--color-error-text, var(--color-element-error)) + ); + --nl-note-text: var( + --nldesign-component-notecard-error-text-color, + var(--prev-text) + ); +} +.nl-mini__note--success { + --nl-note-fill: var( + --nldesign-component-notecard-success-color, + var(--color-success) + ); + --nl-note-icon: var( + --nldesign-component-notecard-success-icon-color, + var(--color-success-text, var(--color-element-success)) + ); + --nl-note-text: var( + --nldesign-component-notecard-success-text-color, + var(--prev-text) + ); +} + +/* Toasts: a card on its own background with the type colour as a stripe, + stacked in the corner above the dialog the way Nextcloud shows them. */ +.nl-mini__toasts { + position: absolute; + right: 8px; + bottom: 8px; + z-index: 1; + display: flex; + flex-direction: column; + gap: 3px; + width: 70px; +} +.nl-mini__toast { + display: block; + padding: 4px 5px; + border-inline-start: 2px solid var(--nl-toast-stripe); + border-radius: var( + --nldesign-component-toast-border-radius, + var(--border-radius, 3px) + ); + background: var(--nl-toast-fill); + box-shadow: 0 0 4px rgba(0, 0, 0, 0.2); +} +.nl-mini__toast-text { + display: block; + height: 3px; + border-radius: 2px; + background: var(--nl-toast-text); + opacity: 0.7; +} +.nl-mini__toast--success { + --nl-toast-fill: var( + --nldesign-component-toast-success-background-color, + var(--prev-surface) + ); + --nl-toast-text: var(--nldesign-component-toast-success-color, var(--prev-text)); + --nl-toast-stripe: var(--color-element-success, var(--color-success)); +} +.nl-mini__toast--error { + --nl-toast-fill: var( + --nldesign-component-toast-error-background-color, + var(--prev-surface) + ); + --nl-toast-text: var(--nldesign-component-toast-error-color, var(--prev-text)); + --nl-toast-stripe: var(--color-element-error, var(--color-error)); +} +.nl-mini__toast--warning { + --nl-toast-fill: var( + --nldesign-component-toast-warning-background-color, + var(--prev-surface) + ); + --nl-toast-text: var(--nldesign-component-toast-warning-color, var(--prev-text)); + --nl-toast-stripe: var(--color-element-warning, var(--color-warning)); +} +.nl-mini__toast--info { + --nl-toast-fill: var( + --nldesign-component-toast-info-background-color, + var(--prev-surface) + ); + --nl-toast-text: var(--nldesign-component-toast-info-color, var(--prev-text)); + --nl-toast-stripe: var(--color-element-info, var(--prev-primary)); +} + +/* The avatar's online dot. */ +.nl-mini__avatar-status { + position: absolute; + right: -1px; + bottom: -1px; + width: 5px; + height: 5px; + border-radius: 50%; + background: var( + --nldesign-component-avatar-online-status-color, + var(--color-success, #2d7b41) + ); +} + +/* Breadcrumbs: a link crumb, a hovered one and the current one. They are + spans, so each sets the on-surface property nldesign's blanket text rule + reads first as well as its colour. */ +.nl-mini__crumbs { + display: flex; + align-items: center; + gap: 2px; + margin-bottom: 6px; + font-size: 8px; +} +.nl-mini__crumb { + padding: 1px 5px; + border-radius: var(--prev-radius-pill); + --nldesign-color-on-surface: var( + --nldesign-component-breadcrumb-color, + var(--color-primary, var(--prev-primary)) + ); + color: var( + --nldesign-component-breadcrumb-color, + var(--color-primary, var(--prev-primary)) + ); +} +.nl-mini__crumb--hover { + background: var( + --nldesign-component-breadcrumb-hover-background-color, + var(--color-background-hover, var(--prev-bg)) + ); +} +.nl-mini__crumb--current { + --nldesign-color-on-surface: var(--prev-text); + color: var(--prev-text); + font-weight: 700; +} +.nl-mini__crumb-sep { + --nldesign-color-on-surface: var( + --nldesign-component-breadcrumb-separator-color, + var(--color-text-maxcontrast, var(--prev-muted)) + ); + color: var( + --nldesign-component-breadcrumb-separator-color, + var(--color-text-maxcontrast, var(--prev-muted)) + ); +} + +/* A form row: text field, checked checkbox, progress bar. */ +.nl-mini__form { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 9px; +} +.nl-mini__input { + flex: 1; + display: flex; + align-items: center; + height: 14px; + padding: 0 6px; + border: 1px solid + var( + --nldesign-component-textbox-border-color, + var(--color-border-maxcontrast, var(--prev-border)) + ); + border-radius: var( + --nldesign-component-textbox-border-radius, + var(--prev-radius) + ); +} +.nl-mini__input-text { + width: 50%; + height: 4px; + border-radius: 2px; + background: var( + --nldesign-component-textbox-placeholder-color, + var(--color-placeholder-dark, var(--prev-muted)) + ); +} +.nl-mini__check { + flex: none; + width: 10px; + height: 10px; + border-radius: var( + --nldesign-component-checkbox-border-radius, + var(--border-radius-small, 3px) + ); + background: var( + --nldesign-component-checkbox-checked-background-color, + var(--color-primary-element, var(--prev-primary)) + ); +} +.nl-mini__progress { + flex: 0 0 50px; + height: 5px; + overflow: hidden; + border-radius: var(--nldesign-component-progress-border-radius, 999px); + background: var( + --nldesign-component-progress-track-background-color, + var(--color-background-darker, var(--prev-border)) + ); +} +.nl-mini__progress-fill { + display: block; + width: 60%; + height: 100%; + background: var( + --nldesign-component-progress-background-color, + var(--color-primary-element, var(--prev-primary)) + ); +} + +/* Typography in the dialog: each text painted from its own token. They are + divs and spans, so each sets the on-surface property as well. */ +.nl-mini__type { + display: flex; + flex-direction: column; + gap: 4px; +} +.nl-mini__type-heading { + --nldesign-color-on-surface: var( + --nldesign-component-heading-color, + var(--color-main-text, var(--prev-text)) + ); + color: var( + --nldesign-component-heading-color, + var(--color-main-text, var(--prev-text)) + ); + font-family: var( + --nldesign-component-heading-font-family, + var(--font-face, inherit) + ); + font-weight: var( + --nldesign-component-heading-font-weight, + var(--font-weight-heading, 700) + ); + font-size: 13px; + line-height: 1.2; +} +.nl-mini__type-paragraph { + --nldesign-color-on-surface: var( + --nldesign-component-paragraph-color, + var(--color-main-text, var(--prev-text)) + ); + color: var( + --nldesign-component-paragraph-color, + var(--color-main-text, var(--prev-text)) + ); + font-family: var( + --nldesign-component-paragraph-font-family, + var(--font-face, inherit) + ); + font-weight: var( + --nldesign-component-paragraph-font-weight, + var(--font-weight-default, 400) + ); + font-size: 9px; +} +.nl-mini__type-link { + --nldesign-color-on-surface: var( + --nldesign-component-link-color, + var(--color-primary, var(--prev-primary)) + ); + color: var( + --nldesign-component-link-color, + var(--color-primary, var(--prev-primary)) + ); + text-decoration: underline; + cursor: pointer; +} +.nl-mini__type-link:hover, +.nl-mini__type-link:focus-visible { + --nldesign-color-on-surface: var( + --nldesign-component-link-hover-color, + var(--color-primary-hover, var(--prev-primary)) + ); + color: var( + --nldesign-component-link-hover-color, + var(--color-primary-hover, var(--prev-primary)) + ); +} +.nl-mini__type-row { + display: flex; + gap: 10px; + font-size: 9px; +} +.nl-mini__type-muted { + --nldesign-color-on-surface: var( + --nldesign-component-text-muted-color, + var(--color-text-maxcontrast, var(--prev-muted)) + ); + color: var( + --nldesign-component-text-muted-color, + var(--color-text-maxcontrast, var(--prev-muted)) + ); +} +.nl-mini__type-light { + --nldesign-color-on-surface: var( + --nldesign-component-text-light-color, + var(--color-text-light, var(--prev-muted)) + ); + color: var( + --nldesign-component-text-light-color, + var(--color-text-light, var(--prev-muted)) + ); +} +.nl-mini__type-lighter { + --nldesign-color-on-surface: var( + --nldesign-component-text-lighter-color, + var(--color-text-lighter, var(--prev-muted)) + ); + color: var( + --nldesign-component-text-lighter-color, + var(--color-text-lighter, var(--prev-muted)) + ); +} +.nl-mini__type-error { + --nldesign-color-on-surface: var( + --nldesign-component-text-error-color, + var(--color-text-error, var(--prev-error)) + ); + color: var( + --nldesign-component-text-error-color, + var(--color-text-error, var(--prev-error)) + ); +} +.nl-mini__type-warning { + --nldesign-color-on-surface: var( + --nldesign-component-text-warning-color, + var(--color-text-warning, var(--prev-warning)) + ); + color: var( + --nldesign-component-text-warning-color, + var(--color-text-warning, var(--prev-warning)) + ); +} +.nl-mini__type-success { + --nldesign-color-on-surface: var( + --nldesign-component-text-success-color, + var(--color-text-success, #2d7b41) + ); + color: var( + --nldesign-component-text-success-color, + var(--color-text-success, #2d7b41) + ); +} + +/* Each tab's full view shows that tab's part of the shell. js/playground.js + names the open tab on the preview; with no tab named (the playground not + loaded) everything is shown, as before. + - Buttons & Status: the dialog with notes, buttons and badge, and the + toasts. + - Typography: the dialog with the texts instead. + - Content area: the app shell itself, with no dialog over it. */ +#nldesign-preview[data-pg-tab='status'] .nl-mini__type, +#nldesign-preview[data-pg-tab='typography'] .nl-mini__notes, +#nldesign-preview[data-pg-tab='typography'] .nl-mini__modal-actions, +#nldesign-preview[data-pg-tab='typography'] .nl-mini__counter, +#nldesign-preview[data-pg-tab='typography'] .nl-mini__toasts, +#nldesign-preview[data-pg-tab='content'] .nl-mini__modal-overlay, +#nldesign-preview[data-pg-tab='content'] .nl-mini__toasts { + display: none; +} + /* modal overlay over the shell */ .nl-mini__modal-overlay { position: absolute; @@ -982,7 +1544,10 @@ .nl-mini__modal { width: 78%; background: var(--prev-surface); - border-radius: var(--border-radius-large); + border-radius: var( + --nldesign-component-dialog-border-radius, + var(--border-radius-large) + ); box-shadow: 0 8px 30px rgba(0, 0, 0, 0.3); padding: 12px; } @@ -1004,10 +1569,13 @@ gap: 6px; } -/* buttons (shared by modal + login). Scoped under .nldesign-preview (0,2,0) so - they beat Nextcloud core's global button chrome (button:not(.button-vue,…) at - 0,1,1), which would otherwise paint every preview button the same pale tint. */ -.nldesign-preview .nl-btn { +/* buttons (shared by modal + login). Scoped under the preview's ID so they beat + Nextcloud core's chrome for plain buttons. Its `:focus` and `:active` rules + (button:not(.button-vue,…):not(:disabled,.primary):not(…):focus, at 0,4,1) + out-ranked a class-only scope: a clicked button kept core's blue border and + pressed fill until something else took the focus. Keyboard focus keeps core's + own `!important` ring. */ +#nldesign-preview .nl-btn { font-size: 9px; font-weight: 700; padding: 5px 10px; @@ -1018,27 +1586,145 @@ color: #fff; min-height: 0; } -.nldesign-preview .nl-btn--primary { - background: var(--prev-primary); - border-color: var(--prev-primary); - color: var(--prev-primary-text); -} -.nldesign-preview .nl-btn--secondary { - background: var(--prev-surface); - border-color: var(--prev-border); - color: var(--prev-text); -} -.nldesign-preview .nl-btn--warning { - background: var(--prev-warning); - border-color: var(--prev-warning); -} -.nldesign-preview .nl-btn--error { - background: var(--prev-error); - border-color: var(--prev-error); -} -.nldesign-preview .nl-btn--info { - background: var(--prev-info); - border-color: var(--prev-info); +/* Each button reads the token of the button it stands for, and the set's + summary colour only when that token is unset. Painted from the summary + alone, every button showed the brand primary whatever its own row said — + saved or still being edited, both of which reach this subtree. */ +#nldesign-preview .nl-btn--primary { + background: var( + --nldesign-component-button-primary-action-background-color, + var(--prev-primary) + ); + border-color: var( + --nldesign-component-button-primary-action-border-color, + var( + --nldesign-component-button-primary-action-background-color, + var(--prev-primary) + ) + ); + color: var( + --nldesign-component-button-primary-action-color, + var(--prev-primary-text) + ); +} +/* Unset, the secondary button is NcButton's own: the light primary fill, its + paired text colour and its hover colour as the edge. Falling back to the + plain surface drew it white where the real one is a light tint. */ +#nldesign-preview .nl-btn--secondary { + background: var( + --nldesign-component-button-secondary-action-background-color, + var(--color-primary-element-light, var(--prev-surface)) + ); + border-color: var( + --nldesign-component-button-secondary-action-border-color, + var(--color-primary-element-light-hover, var(--prev-border)) + ); + color: var( + --nldesign-component-button-secondary-action-color, + var(--color-primary-element-light-text, var(--prev-text)) + ); +} +/* NcButton's tertiary: no fill until it is hovered, labelled in the light + primary text colour. */ +#nldesign-preview .nl-btn--tertiary { + background: transparent; + color: var( + --nldesign-component-button-color, + var(--color-main-text, var(--prev-text)) + ); +} +/* The error and success buttons as NcButton draws them: the status colour as + the fill, its hover colour as the edge, and the status's paired TEXT colour + as the label — Nextcloud's `--color-error-text` / `--color-success-text`, + which is what makes a pale fill readable. White on every fill assumed a + saturated one and lost the label on a set with pale status colours. */ +#nldesign-preview .nl-btn--error { + background: var( + --nldesign-component-button-error-background-color, + var(--prev-error) + ); + border-color: var( + --nldesign-component-button-error-hover-background-color, + var(--color-error-hover, var(--prev-error)) + ); + /* Real error buttons are labelled white by css/error-contrast.css unless + the label token is set, so the mock does the same. */ + color: var(--nldesign-component-button-error-color, #fff); +} +#nldesign-preview .nl-btn--success { + background: var( + --nldesign-component-button-success-background-color, + var(--color-success, #2d7b41) + ); + border-color: var( + --nldesign-component-button-success-hover-background-color, + var(--color-success-hover, var(--color-success, #2d7b41)) + ); + color: var( + --nldesign-component-button-success-color, + var(--color-success-text, #fff) + ); +} +#nldesign-preview .nl-btn--success:hover { + background: var( + --nldesign-component-button-success-hover-background-color, + var(--color-success-hover, var(--color-success, #2d7b41)) + ); +} + +/* Hover, from each button's own hover token. The primary, secondary and error + buttons fall back to their resting colour, so a set without a hover token + shows no change rather than a colour it never chose. */ +#nldesign-preview .nl-btn--primary:hover { + background: var( + --nldesign-component-button-primary-action-hover-background-color, + var( + --nldesign-component-button-primary-action-background-color, + var(--prev-primary) + ) + ); + border-color: var( + --nldesign-component-button-primary-action-hover-background-color, + var( + --nldesign-component-button-primary-action-background-color, + var(--prev-primary) + ) + ); +} +#nldesign-preview .nl-btn--secondary:hover { + background: var( + --nldesign-component-button-secondary-action-hover-background-color, + var(--color-primary-element-light-hover, var(--prev-bg)) + ); +} +#nldesign-preview .nl-btn--tertiary:hover { + background: var( + --nldesign-component-button-hover-background-color, + color-mix( + in srgb, + var(--color-primary-element, var(--prev-primary)) 8%, + transparent + ) + ); +} +#nldesign-preview .nl-btn--tertiary:active { + background: var( + --nldesign-component-button-active-background-color, + var( + --color-primary-element-light, + color-mix(in srgb, var(--prev-primary) 20%, transparent) + ) + ); +} +#nldesign-preview .nl-btn--error:hover { + background: var( + --nldesign-component-button-error-hover-background-color, + var(--nldesign-component-button-error-background-color, var(--prev-error)) + ); + border-color: var( + --nldesign-component-button-error-hover-background-color, + var(--nldesign-component-button-error-background-color, var(--prev-error)) + ); } /* --- mini login --- */ @@ -1053,40 +1739,126 @@ justify-content: center; gap: 14px; } +/* The logo as the login page shows it: the theme's own image. */ .nl-login__logo { - width: 36px; - height: 36px; - border-radius: 50%; - background: var(--prev-primary-text); - opacity: 0.95; + width: 60px; + height: 32px; + background-image: var(--image-logo, url('../../../core/img/logo/logo.svg')); + background-position: center; + background-repeat: no-repeat; + background-size: contain; } +/* The login card: its own corner and edge tokens, falling back to what the + guest box reads. */ .nl-login__card { width: 190px; background: var(--prev-surface); - border-radius: var(--border-radius-large); + border: 1px solid var(--nldesign-component-login-box-border-color, transparent); + border-radius: var( + --nldesign-component-login-box-border-radius, + var(--border-radius-large) + ); box-shadow: 0 8px 30px rgba(0, 0, 0, 0.25); padding: 16px; display: flex; flex-direction: column; gap: 10px; } +/* The account and password fields are text inputs, so they wear the text + input's border, corner and placeholder colour. */ .nl-login__field { - height: 14px; - border-radius: 999px; - background: var(--prev-bg); - border: 1px solid var(--prev-border); -} -.nl-login__submit { + display: flex; + align-items: center; + height: 16px; + padding: 0 7px; + border-radius: var( + --nldesign-component-textbox-border-radius, + var(--prev-radius) + ); + background: var(--prev-surface); + border: 1px solid + var( + --nldesign-component-textbox-border-color, + var(--color-border-maxcontrast, var(--prev-border)) + ); +} +.nl-login__placeholder { + width: 55%; + height: 4px; + border-radius: 2px; + background: var( + --nldesign-component-textbox-placeholder-color, + var(--color-placeholder-dark, var(--prev-muted)) + ); +} +/* The login button: its own tokens first, the primary button's behind them, + the way #body-login resolves it (css/component-scopes.css). Declared after + the shared button rules so it wins at the same specificity. */ +#nldesign-preview .nl-login__submit { align-self: stretch; text-align: center; padding: 7px; -} + background: var( + --nldesign-component-login-button-background-color, + var( + --nldesign-component-button-primary-action-background-color, + var(--prev-primary) + ) + ); + border-color: var( + --nldesign-component-login-button-background-color, + var( + --nldesign-component-button-primary-action-background-color, + var(--prev-primary) + ) + ); + color: var( + --nldesign-component-login-button-color, + var( + --nldesign-component-button-primary-action-color, + var(--prev-primary-text) + ) + ); + border-radius: var( + --nldesign-component-login-button-border-radius, + var(--nldesign-component-button-border-radius, 999px) + ); +} +#nldesign-preview .nl-login__submit:hover { + background: var( + --nldesign-component-login-button-hover-background-color, + var( + --nldesign-component-button-primary-action-hover-background-color, + var( + --nldesign-component-login-button-background-color, + var(--prev-primary) + ) + ) + ); + border-color: var( + --nldesign-component-login-button-hover-background-color, + var( + --nldesign-component-button-primary-action-hover-background-color, + var( + --nldesign-component-login-button-background-color, + var(--prev-primary) + ) + ) + ); +} +/* The slogan in its own colour and typeface. It is a `div`, so it also sets + the on-surface property nldesign's blanket text rule reads first. */ .nl-login__slogan { - width: 130px; - height: 6px; - border-radius: 3px; - background: var(--prev-primary-text); - opacity: 0.6; + --nldesign-color-on-surface: var( + --nldesign-component-logo-slogan-color, + var(--prev-primary-text) + ); + color: var(--nldesign-component-logo-slogan-color, var(--prev-primary-text)); + font-family: var( + --nldesign-component-logo-slogan-font-family, + var(--font-face, inherit) + ); + font-size: 9px; } /* Theme preview ("proefdraaien") controls in the settings panel. */ @@ -1179,139 +1951,121 @@ Colours come from Nextcloud's own variables, so the panel follows the active theme and this app's overrides of it rather than pinning greys of its own. */ -/* `table-layout: fixed` is what stops this panel widening the settings page. - Under the default `auto` the browser sizes every column to its content, and - the Changed cell holds an unbounded list of token names — one - overrides_written entry names forty — so the table grew past 100% and took - the page with it. Fixed layout makes the declared widths binding and the - content wrap inside them. */ -.nldesign-audit-table { - width: 100%; - table-layout: fixed; +/* The panel scrolls SIDEWAYS in its own container at every width, not just on + a phone. Six columns of audit evidence do not fit the settings column on a + laptop either, and the previous answer — `table-layout: fixed` with declared + percentages — did not bound anything: a nowrap timestamp or a set name wider + than its 15% simply drew over the cell beside it, so Timestamp overlapped + User and From overlapped To on every row. + + So the table sizes to its content (`auto`) and is given a floor wide enough + for all six columns to be read; whatever the settings column cannot show is + reached by scrolling the container. The container, not the : + `display: block` on a table drops its implicit table role in Chrome and + Firefox, which unassociates every `th[scope=col]` from its cells, and this + is the panel shown to an accessibility auditor. + + The container is focusable because an `overflow-x: auto` element that cannot + take focus cannot be scrolled without a pointer (SC 2.1.1). It carries a + role and a name so the focus stop announces itself rather than being a + silent one. */ +.nldesign-audit-scroll { + max-width: 100%; + overflow-x: auto; + /* The gap to the download button below; the table itself no longer carries + a bottom margin, which would fall inside the container border. */ margin-bottom: 1em; - border-collapse: collapse; - font-size: 0.9em; -} - -/* Timestamp, User, Action, From, To, Changed. The first five are bounded by - what they hold — an ISO timestamp, an account name, the app's own action - vocabulary, two summarised values — so Changed gets the remainder. */ -.nldesign-audit-table th:nth-child(1) { - width: 15%; -} - -.nldesign-audit-table th:nth-child(2) { - width: 10%; -} - -.nldesign-audit-table th:nth-child(3) { - width: 16%; + border: 1px solid var(--color-border); + border-radius: var(--border-radius-large, 8px); + background-color: var(--color-main-background); } -.nldesign-audit-table th:nth-child(4), -.nldesign-audit-table th:nth-child(5) { - width: 11%; +.nldesign-audit-scroll:focus-visible { + outline: 2px solid var(--color-primary-element); + outline-offset: 2px; } -.nldesign-audit-table th:nth-child(6) { - width: 37%; +.nldesign-audit-table { + width: 100%; + /* Wide enough that no column has to be squeezed below what it holds. Under + the settings column this is exceeded, which is what the scroller is for. */ + min-width: 72em; + table-layout: auto; + margin-bottom: 0; + border-collapse: collapse; + font-size: 0.9em; } .nldesign-audit-table th, .nldesign-audit-table td { - padding: 6px 12px 6px 0; + padding: 10px 16px; text-align: start; vertical-align: top; border-bottom: 1px solid var(--color-border); } -/* The header rule is the darker one: it separates a label row from data, which - is a stronger break than the one between two data rows. */ +/* The header is a label row over data, a stronger break than the one between + two data rows: it gets the darker rule and a tint of its own. */ .nldesign-audit-table thead th { + background-color: var(--color-background-dark); border-bottom: 2px solid var(--color-border-dark, var(--color-border)); color: var(--color-text-maxcontrast); font-weight: 600; white-space: nowrap; } -.nldesign-audit-table tbody tr:hover { +/* Banding carries the eye across six columns on a row that is wider than the + viewport — the case this panel is always in. Hover wins over the band. */ +.nldesign-audit-table tbody tr:nth-child(even) { background-color: var(--color-background-hover); } -/* A timestamp and an actor are identifiers and must not be broken across - lines; the action is the app's own vocabulary, which has no spaces to break - on anyway. */ -.nldesign-audit-ts, -.nldesign-audit-actor { - white-space: nowrap; +.nldesign-audit-table tbody tr:hover { + background-color: var(--color-background-dark); } -/* The action wraps rather than being pinned: under a fixed layout a nowrap - cell overflows its column instead of widening it, and `theming_sync_applied` - is longer than its share. Underscores give it somewhere to break. */ -.nldesign-audit-action { - font-family: var(--font-face-monospace, monospace); - overflow-wrap: anywhere; +/* The last row's rule would double the container's own border. */ +.nldesign-audit-table tbody tr:last-child td { + border-bottom: none; } -/* The changed list is the unbounded one — every token name a write touched. - It wraps inside its column and breaks mid-name if it has to, because a - forty-name list has to be readable without the table escaping the page. */ -.nldesign-audit-changed { - overflow-wrap: anywhere; +/* A timestamp, an actor and an action are identifiers: they are compared down + the column, so they hold one line each and the table widens for them rather + than breaking them in half. */ +.nldesign-audit-ts, +.nldesign-audit-actor, +.nldesign-audit-action { + white-space: nowrap; } -/* The two value columns are the ones that can hold something long — a custom - token set name an admin chose, or a digest. They are the only columns - allowed to wrap, and they break inside a word because a sha256 digest has - nowhere else to break. */ -.nldesign-audit-value { - width: 22%; - overflow-wrap: anywhere; +.nldesign-audit-ts { + font-variant-numeric: tabular-nums; + color: var(--color-text-maxcontrast); } -/* Below the mobile breakpoint the columns cannot all be read at once, so the - table scrolls — in a real CONTAINER, not by making the table itself the - scroller. `display: block` on a
drops its implicit table role in - Chrome and Firefox, which unassociates every `th[scope=col]` from its - cells; on the panel that exists to be shown to an accessibility auditor, - that is the one thing it must not do. - - The container is focusable because an `overflow-x: auto` element that - cannot take focus cannot be scrolled without a pointer. It carries a role - and a name so the focus stop announces itself rather than being a silent +/* The action is the app's own vocabulary — a key, not prose — so it is set as one. */ -.nldesign-audit-scroll { - max-width: 100%; +.nldesign-audit-action { + font-family: var(--font-face-monospace, monospace); + font-size: 0.95em; } -.nldesign-audit-scroll:focus-visible { - outline: 2px solid var(--color-primary-element); - outline-offset: 2px; +/* From and To hold the one thing a reader compares across, so they line up at + a common width and wrap inside it: a custom token set name an admin chose or + a sha256 digest has nowhere else to break. */ +.nldesign-audit-value { + width: 12em; + min-width: 9em; + overflow-wrap: anywhere; } -@media (max-width: 480px) { - .nldesign-audit-scroll { - overflow-x: auto; - } - - /* Back to auto: here the table is MEANT to exceed the viewport and be - scrolled, which a fixed layout cannot do — its columns would keep their - percentages and the content would spill out of them instead. */ - .nldesign-audit-table { - table-layout: auto; - min-width: 40em; - white-space: nowrap; - } - - .nldesign-audit-table th:nth-child(1), - .nldesign-audit-table th:nth-child(2), - .nldesign-audit-table th:nth-child(3), - .nldesign-audit-table th:nth-child(4), - .nldesign-audit-table th:nth-child(5), - .nldesign-audit-table th:nth-child(6) { - width: auto; - } +/* Changed is the unbounded column — every token name a write touched, forty of + them on one overrides_written entry. It gets a preferred width and wraps + inside it instead of stretching the row past any useful scroll distance. */ +.nldesign-audit-changed { + width: 26em; + color: var(--color-text-maxcontrast); + overflow-wrap: anywhere; } /* ========================================================================== @@ -1330,3 +2084,90 @@ animation: none !important; } } + +/* ========================================================================== + SAVE CONFIRMATION + ========================================================================== */ + +/* The plain "are you sure" is narrower than the dialogs that carry a table: + it holds two lines and three controls, and at the shared width it reads as + an emptier version of a bigger question than it is asking. */ +.nldesign-dialog--small { + max-width: 420px; +} + +/* The name field in the stock dialog. Stacked rather than inline, because the + label is a sentence and a side-by-side field pushes it into two lines at the + widths this dialog actually gets. */ +.nldesign-dialog-field { + display: block; + margin: 16px 0 8px; + font-weight: 500; +} + +.nldesign-dialog-field input[type='text'] { + display: block; + width: 100%; + margin-top: 4px; + font-weight: normal; +} + +.nldesign-dialog-field input[aria-invalid='true'] { + border-color: var(--color-error); +} + +/* "Do not ask again" sits with the question, not with the buttons: it belongs + to what is being asked, and putting it in the action bar made it read as a + third choice beside Cancel and Save. */ +.nldesign-dialog-dontask-row { + display: flex; + gap: 8px; + align-items: center; + margin-top: 16px; + color: var(--color-text-maxcontrast); +} + +/* The way back, under the editor. Deliberately quiet — it is a preference + about dialogs, not a theming control, and it must not compete with the token + rows above it for an admin's attention. */ +.nldesign-confirm-controls { + display: flex; + flex-direction: column; + gap: 4px; + margin-top: 12px; + padding-top: 12px; + border-top: 1px solid var(--color-border); + color: var(--color-text-maxcontrast); + font-size: 0.9em; +} + +.nldesign-confirm-controls label { + display: flex; + gap: 8px; + align-items: center; + cursor: pointer; +} + +/* The opt-in to Nextcloud's own base tokens, above the tabs. Quiet for the + same reason: the component rows are the everyday control. */ +.nldesign-base-unlock { + display: flex; + gap: 8px; + align-items: center; + margin: 8px 0; + color: var(--color-text-maxcontrast); + font-size: 0.9em; + cursor: pointer; +} + +/* The server's refusal, inside the dialog that caused it. `hidden` is honoured + explicitly because the flex/block display below would otherwise beat it. */ +.nldesign-dialog-error { + margin: 8px 0 0; + color: var(--color-error-text, var(--color-error)); + font-size: 0.9em; +} + +.nldesign-dialog-error[hidden] { + display: none; +} diff --git a/css/component-scopes.css b/css/component-scopes.css new file mode 100644 index 00000000..0b66cd65 --- /dev/null +++ b/css/component-scopes.css @@ -0,0 +1,964 @@ +/** + * GENERATED FILE — do not edit. + * + * SPDX-License-Identifier: EUPL-1.2 + * SPDX-FileCopyrightText: 2026 Conduction B.V. + * + * Source: scripts/mapping/component-tokens.json + * Regenerate: npm run generate:component-scopes + */ + +/* + * Component-scoped tokens. + * + * Each rule below redeclares, inside one component's subtree, the Nextcloud + * variable that component consumes — so the component follows its own token + * when one is set and the brand-wide global when one is not. Nextcloud's own + * stylesheets keep doing the painting; only the value they see changes. + * + * The capture block copies each global under a name the scopes can fall back + * to. Falling back to the global directly would be a self-referential custom + * property, which CSS discards as invalid. + * + * IT IS `body`, NOT `:root`, AND THAT IS THE WHOLE POINT. + * + * Every design system declares the Nextcloud globals on `body` — + * css/systems/nldesign/theme.css does, and so do the high-contrast, + * summer-breeze and lasuite sheets. Nextcloud core declares its OWN on + * `:root`, because ThemeInjectionService serves the default theme with + * `plain=true`. So a capture taken on `:root` copied core's value and never + * the token set's, and every component whose token nobody had set was painted + * Nextcloud blue instead of the brand colour. + * + * `body` sees both: what it declares itself, and whatever `:root` declares, + * by inheritance. There is no element between `body` and the components that + * declares a global, so nothing is missed. + * + * The token editor's preview is the one exception, and it is captured again. + * An unsaved edit is declared on `#nldesign-preview` and nowhere else — it is + * a preview until it is saved — so a capture taken only on `body` would hand + * the specimens inside the preview the saved value instead of the edit. + */ + +body, +#nldesign-preview { + --thematiq-global-color-background-plain: var(--color-background-plain); + --thematiq-global-color-background-plain-text: var(--color-background-plain-text); + --thematiq-global-color-background-hover: var(--color-background-hover); + --thematiq-global-border-radius-large: var(--border-radius-large); + --thematiq-global-color-border: var(--color-border); + --thematiq-global-color-primary-element: var(--color-primary-element); + --thematiq-global-color-primary-element-text: var(--color-primary-element-text); + --thematiq-global-color-primary-element-hover: var(--color-primary-element-hover); + --thematiq-global-border-radius-element: var(--border-radius-element); + --thematiq-global-color-primary-text: var(--color-primary-text); + --thematiq-global-font-face: var(--font-face); + --thematiq-global-color-main-text: var(--color-main-text); + --thematiq-global-body-container-radius: var(--body-container-radius); + --thematiq-global-border-radius-rounded: var(--border-radius-rounded); + --thematiq-global-color-scrollbar: var(--color-scrollbar); + --thematiq-global-color-text-maxcontrast: var(--color-text-maxcontrast); + --thematiq-global-color-main-background: var(--color-main-background); + --thematiq-global-font-weight-default: var(--font-weight-default); + --thematiq-global-color-background-dark: var(--color-background-dark); + --thematiq-global-color-border-dark: var(--color-border-dark); + --thematiq-global-animation-quick: var(--animation-quick); + --thematiq-global-color-primary-light: var(--color-primary-light); + --thematiq-global-color-primary-light-text: var(--color-primary-light-text); + --thematiq-global-color-primary-light-hover: var(--color-primary-light-hover); + --thematiq-global-color-success: var(--color-success); + --thematiq-global-color-primary: var(--color-primary); + --thematiq-global-color-border-maxcontrast: var(--color-border-maxcontrast); + --thematiq-global-color-placeholder-dark: var(--color-placeholder-dark); + --thematiq-global-color-placeholder-light: var(--color-placeholder-light); + --thematiq-global-color-border-error: var(--color-border-error); + --thematiq-global-vs-border-color: var(--vs-border-color); + --thematiq-global-vs-border-radius: var(--vs-border-radius); + --thematiq-global-vs-selected-color: var(--vs-selected-color); + --thematiq-global-color-primary-element-light: var(--color-primary-element-light); + --thematiq-global-vs-dropdown-color: var(--vs-dropdown-color); + --thematiq-global-vs-dropdown-option--active-bg: var(--vs-dropdown-option--active-bg); + --thematiq-global-vs-dropdown-option--active-color: var(--vs-dropdown-option--active-color); + --thematiq-global-border-radius-small: var(--border-radius-small); + --thematiq-global-animation-slow: var(--animation-slow); + --thematiq-global-color-background-darker: var(--color-background-darker); + --thematiq-global-border-radius-pill: var(--border-radius-pill); + --thematiq-global-font-weight-element: var(--font-weight-element); + --thematiq-global-color-primary-element-light-text: var(--color-primary-element-light-text); + --thematiq-global-color-primary-element-light-hover: var(--color-primary-element-light-hover); + --thematiq-global-color-error: var(--color-error); + --thematiq-global-color-error-rgb: var(--color-error-rgb); + --thematiq-global-color-error-hover: var(--color-error-hover); + --thematiq-global-color-error-text: var(--color-error-text); + --thematiq-global-color-success-text: var(--color-success-text); + --thematiq-global-color-success-hover: var(--color-success-hover); + --thematiq-global-color-info: var(--color-info); + --thematiq-global-color-info-text: var(--color-info-text); + --thematiq-global-color-info-rgb: var(--color-info-rgb); + --thematiq-global-border-radius: var(--border-radius); + --thematiq-global-color-warning: var(--color-warning); + --thematiq-global-color-warning-text: var(--color-warning-text); + --thematiq-global-color-warning-rgb: var(--color-warning-rgb); + --thematiq-global-color-success-rgb: var(--color-success-rgb); + --thematiq-global-color-favorite: var(--color-favorite); + --thematiq-global-font-weight-heading: var(--font-weight-heading); + --thematiq-global-color-primary-hover: var(--color-primary-hover); + --thematiq-global-color-text-light: var(--color-text-light); + --thematiq-global-color-text-lighter: var(--color-text-lighter); + --thematiq-global-color-text-error: var(--color-text-error); + --thematiq-global-color-text-warning: var(--color-text-warning); + --thematiq-global-color-text-success: var(--color-text-success); + + /* Component tokens that a component redirects onto its own. */ + --thematiq-alias-nldesign-component-button-primary-action-background-color: var(--nldesign-component-button-primary-action-background-color); + --thematiq-alias-nldesign-component-button-primary-action-border-color: var(--nldesign-component-button-primary-action-border-color); + --thematiq-alias-nldesign-component-button-primary-action-color: var(--nldesign-component-button-primary-action-color); + --thematiq-alias-nldesign-component-button-primary-action-hover-background-color: var(--nldesign-component-button-primary-action-hover-background-color); + --thematiq-alias-nldesign-component-button-border-radius: var(--nldesign-component-button-border-radius); + --thematiq-alias-nldesign-component-textbox-border-color: var(--nldesign-component-textbox-border-color); + --thematiq-alias-nldesign-component-textbox-border-radius: var(--nldesign-component-textbox-border-radius); + --thematiq-alias-nldesign-component-textbox-background-color: var(--nldesign-component-textbox-background-color); + --thematiq-alias-nldesign-component-textbox-focus-border-color: var(--nldesign-component-textbox-focus-border-color); + --thematiq-alias-nldesign-component-button-primary-action-font-weight: var(--nldesign-component-button-primary-action-font-weight); +} + +/* header-bar */ +#header, +[data-thematiq-component='header-bar'] { + --color-background-plain-text: var( + --nldesign-component-header-color, + var(--thematiq-global-color-background-plain-text) + ); + --color-background-hover: var( + --nldesign-component-header-hover-background-color, + var(--thematiq-global-color-background-hover) + ); +} + +#header { + background-color: var(--nldesign-component-header-background-color, transparent); +} + +/* login-card */ +#body-login .guest-box, +#body-login .login-box, +[data-thematiq-component='login-card'] { + --border-radius-large: var( + --nldesign-component-login-box-border-radius, + var(--thematiq-global-border-radius-large) + ); +} + +#body-login .guest-box.login-box { + border: 1px solid var(--nldesign-component-login-box-border-color) !important; +} + +/* login-button */ +#body-login .button-vue--primary, +#body-login .button-vue--vue-primary, +#body-login input[type='submit'], +#body-login button.primary, +.login-form button[type='submit'], +[data-thematiq-component='login-button'] { + --color-primary-element: var( + --nldesign-component-login-button-background-color, + var(--thematiq-global-color-primary-element) + ); + --color-primary-element-text: var( + --nldesign-component-login-button-color, + var(--thematiq-global-color-primary-element-text) + ); + --color-primary-element-hover: var( + --nldesign-component-login-button-hover-background-color, + var(--thematiq-global-color-primary-element-hover) + ); + --border-radius-element: var( + --nldesign-component-login-button-border-radius, + var(--thematiq-global-border-radius-element) + ); + --nldesign-component-button-primary-action-background-color: var( + --nldesign-component-login-button-background-color, + var(--thematiq-alias-nldesign-component-button-primary-action-background-color) + ); + --nldesign-component-button-primary-action-border-color: var( + --nldesign-component-login-button-background-color, + var(--thematiq-alias-nldesign-component-button-primary-action-border-color) + ); + --nldesign-component-button-primary-action-color: var( + --nldesign-component-login-button-color, + var(--thematiq-alias-nldesign-component-button-primary-action-color) + ); + --nldesign-component-button-primary-action-hover-background-color: var( + --nldesign-component-login-button-hover-background-color, + var(--thematiq-alias-nldesign-component-button-primary-action-hover-background-color) + ); + --nldesign-component-button-border-radius: var( + --nldesign-component-login-button-border-radius, + var(--thematiq-alias-nldesign-component-button-border-radius) + ); +} + +/* logo-slogan */ +#body-login .header-guest, +#body-login .logo, +[data-thematiq-component='logo-slogan'] { + --color-primary-text: var( + --nldesign-component-logo-slogan-color, + var(--thematiq-global-color-primary-text) + ); + --font-face: var( + --nldesign-component-logo-slogan-font-family, + var(--thematiq-global-font-face) + ); +} + +/* app-navigation */ +#app-navigation, +#app-navigation-vue, +.app-navigation, +[data-thematiq-component='app-navigation'] { + --border-radius-element: var( + --nldesign-component-navigation-border-radius, + var(--thematiq-global-border-radius-element) + ); + --color-main-text: var( + --nldesign-component-navigation-color, + var(--thematiq-global-color-main-text) + ); + --color-primary-element: var( + --nldesign-component-navigation-active-background-color, + var(--thematiq-global-color-primary-element) + ); +} + +/* content-card */ +#app-content, +.app-content, +#content, +[data-thematiq-component='content-card'] { + --body-container-radius: var( + --nldesign-component-content-card-container-border-radius, + var(--thematiq-global-body-container-radius) + ); + --border-radius-rounded: var( + --nldesign-component-content-card-border-radius, + var(--thematiq-global-border-radius-rounded) + ); + --color-border: var( + --nldesign-component-content-card-border-color, + var(--thematiq-global-color-border) + ); + --color-scrollbar: var( + --nldesign-component-content-card-scrollbar-color, + var(--thematiq-global-color-scrollbar) + ); +} + +/* table */ +table, +[data-thematiq-component='table'] { + --color-text-maxcontrast: var( + --nldesign-component-table-header-color, + var(--thematiq-global-color-text-maxcontrast) + ); + --color-border: var( + --nldesign-component-table-border-color, + var(--thematiq-global-color-border) + ); + --color-background-dark: var( + --nldesign-component-table-zebra-background-color, + var(--thematiq-global-color-background-dark) + ); + --color-background-hover: var( + --nldesign-component-table-row-hover-background-color, + var(--thematiq-global-color-background-hover) + ); +} + +th { + background-color: var(--nldesign-component-table-header-background-color, transparent); + font-weight: var(--nldesign-component-table-header-font-weight, normal); +} + +/* sidebar */ +#app-sidebar, +#app-sidebar-vue, +.app-sidebar, +[data-thematiq-component='sidebar'] { + --color-border: var( + --nldesign-component-sidebar-border-color, + var(--thematiq-global-color-border) + ); + --color-border-dark: var( + --nldesign-component-sidebar-divider-color, + var(--thematiq-global-color-border-dark) + ); + --border-radius-large: var( + --nldesign-component-sidebar-border-radius, + var(--thematiq-global-border-radius-large) + ); + --color-primary-element: var( + --nldesign-component-sidebar-active-color, + var(--thematiq-global-color-primary-element) + ); +} + +/* list-item */ +.list-item, +.list-item__wrapper, +[data-thematiq-component='list-item'] { + --border-radius-element: var( + --nldesign-component-list-item-border-radius, + var(--thematiq-global-border-radius-element) + ); + --color-text-maxcontrast: var( + --nldesign-component-list-item-subline-color, + var(--thematiq-global-color-text-maxcontrast) + ); + --color-background-hover: var( + --nldesign-component-list-item-hover-background-color, + var(--thematiq-global-color-background-hover) + ); + --animation-quick: var( + --nldesign-component-list-item-transition-duration, + var(--thematiq-global-animation-quick) + ); + --color-primary-light: var( + --nldesign-component-list-item-selected-background-color, + var(--thematiq-global-color-primary-light) + ); + --color-primary-light-text: var( + --nldesign-component-list-item-selected-color, + var(--thematiq-global-color-primary-light-text) + ); + --color-primary-light-hover: var( + --nldesign-component-list-item-selected-hover-background-color, + var(--thematiq-global-color-primary-light-hover) + ); +} + +/* empty-content */ +.empty-content, +.empty-content__name, +[data-thematiq-component='empty-content'] { + --color-main-text: var( + --nldesign-component-empty-content-color, + var(--thematiq-global-color-main-text) + ); + --color-text-maxcontrast: var( + --nldesign-component-empty-content-description-color, + var(--thematiq-global-color-text-maxcontrast) + ); + --color-background-dark: var( + --nldesign-component-empty-content-icon-background-color, + var(--thematiq-global-color-background-dark) + ); +} + +/* actions-menu */ +.popover, +.popovermenu, +.v-popper__popper, +[data-thematiq-component='actions-menu'] { + --color-main-text: var( + --nldesign-component-actions-menu-color, + var(--thematiq-global-color-main-text) + ); + --border-radius-large: var( + --nldesign-component-actions-menu-border-radius, + var(--thematiq-global-border-radius-large) + ); + --color-background-hover: var( + --nldesign-component-actions-menu-hover-background-color, + var(--thematiq-global-color-background-hover) + ); +} + +/* avatar */ +.user-status-icon { + --user-status-color-online: var(--nldesign-component-avatar-online-status-color, #2D7B41) !important; +} + +/* breadcrumbs */ +.breadcrumb, +.vue-crumb, +[data-thematiq-component='breadcrumbs'] { + --color-primary: var( + --nldesign-component-breadcrumb-color, + var(--thematiq-global-color-primary) + ); + --color-text-maxcontrast: var( + --nldesign-component-breadcrumb-separator-color, + var(--thematiq-global-color-text-maxcontrast) + ); + --color-background-hover: var( + --nldesign-component-breadcrumb-hover-background-color, + var(--thematiq-global-color-background-hover) + ); +} + +/* settings-section */ +.settings-section, +.app-settings-section, +.settings-section__name, +[data-thematiq-component='settings-section'] { + --color-main-text: var( + --nldesign-component-settings-section-heading-color, + var(--thematiq-global-color-main-text) + ); + --color-text-maxcontrast: var( + --nldesign-component-settings-section-hint-color, + var(--thematiq-global-color-text-maxcontrast) + ); + --color-border: var( + --nldesign-component-settings-section-border-color, + var(--thematiq-global-color-border) + ); +} + +/* text-input */ +.input-field, +input[type='text'], +input[type='password'], +input[type='email'], +input[type='search'], +[data-thematiq-component='text-input'] { + --color-border-maxcontrast: var( + --nldesign-component-textbox-border-color, + var(--thematiq-global-color-border-maxcontrast) + ); + --color-placeholder-light: var( + --nldesign-component-textbox-placeholder-light-color, + var(--thematiq-global-color-placeholder-light) + ); + --border-radius-element: var( + --nldesign-component-textbox-border-radius, + var(--thematiq-global-border-radius-element) + ); + --color-border-error: var( + --nldesign-component-textbox-invalid-border-color, + var(--thematiq-global-color-border-error) + ); +} + +.input-field__input::placeholder { + color: var(--nldesign-component-textbox-placeholder-color, var(--color-text-maxcontrast)) !important; +} + +.input-field__input:focus:not([disabled]), +.input-field__input:active:not([disabled]) { + --input-border-color: var(--nldesign-component-textbox-focus-border-color, var(--color-main-text)) !important; +} + +/* select */ +select, +.v-select, +.vs__dropdown-menu, +[data-thematiq-component='select'] { + --vs-border-color: var( + --nldesign-component-form-select-border-color, + var(--thematiq-global-vs-border-color) + ); + --vs-border-radius: var( + --nldesign-component-form-select-border-radius, + var(--thematiq-global-vs-border-radius) + ); + --vs-selected-color: var( + --nldesign-component-form-select-color, + var(--thematiq-global-vs-selected-color) + ); + --color-primary-element-light: var( + --nldesign-component-form-select-selected-background-color, + var(--thematiq-global-color-primary-element-light) + ); + --vs-dropdown-color: var( + --nldesign-component-form-select-option-color, + var(--thematiq-global-vs-dropdown-color) + ); + --vs-dropdown-option--active-bg: var( + --nldesign-component-form-select-option-hover-background-color, + var(--thematiq-global-vs-dropdown-option--active-bg) + ); + --vs-dropdown-option--active-color: var( + --nldesign-component-form-select-option-hover-color, + var(--thematiq-global-vs-dropdown-option--active-color) + ); +} + +/* checkbox-switch */ +.checkbox-radio-switch, +input[type='checkbox'], +input[type='radio'], +[data-thematiq-component='checkbox-switch'] { + --border-radius-small: var( + --nldesign-component-checkbox-border-radius, + var(--thematiq-global-border-radius-small) + ); + --color-primary-element: var( + --nldesign-component-checkbox-checked-background-color, + var(--thematiq-global-color-primary-element) + ); +} + +.checkbox-radio-switch-checkbox:not(.checkbox-radio-switch--checked) .checkbox-content__icon > *, +.checkbox-radio-switch-radio:not(.checkbox-radio-switch--checked) .checkbox-content__icon > * { + color: var(--nldesign-component-checkbox-border-color, var(--color-primary-element)); +} + +/* textarea */ +textarea, +.textarea, +[data-thematiq-component='textarea'] { + --color-border-maxcontrast: var( + --nldesign-component-textarea-border-color, + var(--thematiq-global-color-border-maxcontrast) + ); + --border-radius-element: var( + --nldesign-component-textarea-border-radius, + var(--thematiq-global-border-radius-element) + ); + --color-main-text: var( + --nldesign-component-textarea-color, + var(--thematiq-global-color-main-text) + ); + --color-main-background: var( + --nldesign-component-textarea-background-color, + var(--thematiq-global-color-main-background) + ); + --color-border-error: var( + --nldesign-component-textarea-invalid-border-color, + var(--thematiq-global-color-border-error) + ); + --nldesign-component-textbox-border-color: var( + --nldesign-component-textarea-border-color, + var(--thematiq-alias-nldesign-component-textbox-border-color) + ); + --nldesign-component-textbox-border-radius: var( + --nldesign-component-textarea-border-radius, + var(--thematiq-alias-nldesign-component-textbox-border-radius) + ); + --nldesign-component-textbox-background-color: var( + --nldesign-component-textarea-background-color, + var(--thematiq-alias-nldesign-component-textbox-background-color) + ); + --nldesign-component-textbox-focus-border-color: var( + --nldesign-component-textarea-focus-border-color, + var(--thematiq-alias-nldesign-component-textbox-focus-border-color) + ); +} + +.textarea__input::placeholder { + color: var(--nldesign-component-textarea-placeholder-color, var(--color-text-maxcontrast)) !important; +} + +.textarea__input:focus:not([disabled]), +.textarea__input:active:not([disabled]) { + --input-border-color: var(--nldesign-component-textarea-focus-border-color, var(--color-main-text)) !important; +} + +/* dialog */ +.modal-container, +.modal-wrapper, +.dialog, +[data-thematiq-component='dialog'] { + --border-radius-large: var( + --nldesign-component-dialog-border-radius, + var(--thematiq-global-border-radius-large) + ); + --color-border: var( + --nldesign-component-dialog-border-color, + var(--thematiq-global-color-border) + ); + --animation-slow: var( + --nldesign-component-dialog-transition-duration, + var(--thematiq-global-animation-slow) + ); + --color-primary-element: var( + --nldesign-component-dialog-confirm-background-color, + var(--thematiq-global-color-primary-element) + ); + --nldesign-component-button-primary-action-background-color: var( + --nldesign-component-dialog-confirm-background-color, + var(--thematiq-alias-nldesign-component-button-primary-action-background-color) + ); + --nldesign-component-button-primary-action-border-color: var( + --nldesign-component-dialog-confirm-background-color, + var(--thematiq-alias-nldesign-component-button-primary-action-border-color) + ); +} + +/* progress */ +progress, +.progress-bar, +.icon-loading, +[data-thematiq-component='progress'] { + --color-primary-element: var( + --nldesign-component-progress-background-color, + var(--thematiq-global-color-primary-element) + ); + --color-background-darker: var( + --nldesign-component-progress-track-background-color, + var(--thematiq-global-color-background-darker) + ); + --border-radius-pill: var( + --nldesign-component-progress-border-radius, + var(--thematiq-global-border-radius-pill) + ); + --animation-slow: var( + --nldesign-component-progress-transition-duration, + var(--thematiq-global-animation-slow) + ); +} + +/* primary-button */ +.button-vue--primary, +.button-vue--vue-primary, +button.primary, +input[type='submit'], +[data-thematiq-component='primary-button'] { + --color-primary-element: var( + --nldesign-component-button-primary-action-background-color, + var(--thematiq-global-color-primary-element) + ); + --color-primary-element-text: var( + --nldesign-component-button-primary-action-color, + var(--thematiq-global-color-primary-element-text) + ); + --color-primary-element-hover: var( + --nldesign-component-button-primary-action-hover-background-color, + var(--thematiq-global-color-primary-element-hover) + ); + --color-main-text: var( + --nldesign-component-button-focus-border-color, + var(--thematiq-global-color-main-text) + ); + --border-radius-element: var( + --nldesign-component-button-border-radius, + var(--thematiq-global-border-radius-element) + ); + --font-weight-element: var( + --nldesign-component-button-font-weight, + var(--thematiq-global-font-weight-element) + ); + --nldesign-component-button-primary-action-font-weight: var( + --nldesign-component-button-font-weight, + var(--thematiq-alias-nldesign-component-button-primary-action-font-weight) + ); +} + +/* secondary-button */ +.button-vue--secondary, +.button-vue--vue-secondary, +[data-thematiq-component='secondary-button'] { + --color-primary-element-light: var( + --nldesign-component-button-secondary-action-background-color, + var(--thematiq-global-color-primary-element-light) + ); + --color-primary-element-light-text: var( + --nldesign-component-button-secondary-action-color, + var(--thematiq-global-color-primary-element-light-text) + ); + --color-primary-element-light-hover: var( + --nldesign-component-button-secondary-action-hover-background-color, + var(--thematiq-global-color-primary-element-light-hover) + ); + --border-radius-element: var( + --nldesign-component-button-secondary-action-border-radius, + var(--thematiq-global-border-radius-element) + ); + --nldesign-component-button-border-radius: var( + --nldesign-component-button-secondary-action-border-radius, + var(--thematiq-alias-nldesign-component-button-border-radius) + ); +} + +/* tertiary-button */ +.button-vue--tertiary, +.button-vue--vue-tertiary, +.button-vue--tertiary-no-background, +[data-thematiq-component='tertiary-button'] { + --color-main-text: var( + --nldesign-component-button-color, + var(--thematiq-global-color-main-text) + ); + --color-primary-element-light: var( + --nldesign-component-button-active-background-color, + var(--thematiq-global-color-primary-element-light) + ); +} + +.button-vue--tertiary:not(.button-vue--tertiary-no-background):not(.button-vue--tertiary-on-primary):hover:not(:disabled) { + background-color: var(--nldesign-component-button-hover-background-color, color-mix(in srgb, var(--color-primary-element) 8%, transparent)) !important; +} + +/* error-button */ +.button-vue--error, +.button-vue--vue-error, +[data-thematiq-component='error-button'] { + --color-error: var( + --nldesign-component-button-error-background-color, + var(--thematiq-global-color-error) + ); + --color-error-rgb: var( + --nldesign-component-button-error-background-color-rgb, + var(--thematiq-global-color-error-rgb) + ); + --color-error-hover: var( + --nldesign-component-button-error-hover-background-color, + var(--thematiq-global-color-error-hover) + ); + --color-error-text: var( + --nldesign-component-button-error-color, + var(--thematiq-global-color-error-text) + ); +} + +/* success-button */ +.button-vue--success, +.button-vue--vue-success, +[data-thematiq-component='success-button'] { + --color-success: var( + --nldesign-component-button-success-background-color, + var(--thematiq-global-color-success) + ); + --color-success-text: var( + --nldesign-component-button-success-color, + var(--thematiq-global-color-success-text) + ); + --color-success-hover: var( + --nldesign-component-button-success-hover-background-color, + var(--thematiq-global-color-success-hover) + ); +} + +/* note-cards */ +.notecard, +[data-thematiq-component='note-cards'] { + --color-info: var( + --nldesign-component-notecard-info-color, + var(--thematiq-global-color-info) + ); + --color-info-text: var( + --nldesign-component-notecard-info-icon-color, + var(--thematiq-global-color-info-text) + ); + --color-info-rgb: var( + --nldesign-component-notecard-info-color-rgb, + var(--thematiq-global-color-info-rgb) + ); + --border-radius: var( + --nldesign-component-notecard-border-radius, + var(--thematiq-global-border-radius) + ); + --color-warning: var( + --nldesign-component-notecard-warning-color, + var(--thematiq-global-color-warning) + ); + --color-warning-text: var( + --nldesign-component-notecard-warning-icon-color, + var(--thematiq-global-color-warning-text) + ); + --color-warning-rgb: var( + --nldesign-component-notecard-warning-color-rgb, + var(--thematiq-global-color-warning-rgb) + ); + --color-error: var( + --nldesign-component-notecard-error-color, + var(--thematiq-global-color-error) + ); + --color-error-text: var( + --nldesign-component-notecard-error-icon-color, + var(--thematiq-global-color-error-text) + ); + --color-error-rgb: var( + --nldesign-component-notecard-error-color-rgb, + var(--thematiq-global-color-error-rgb) + ); + --color-success: var( + --nldesign-component-notecard-success-color, + var(--thematiq-global-color-success) + ); + --color-success-text: var( + --nldesign-component-notecard-success-icon-color, + var(--thematiq-global-color-success-text) + ); + --color-success-rgb: var( + --nldesign-component-notecard-success-color-rgb, + var(--thematiq-global-color-success-rgb) + ); +} + +body .notecard.notecard--info, +body .notecard.info { + --thematiq-contrast-notecard-info-text-color: lch(from var(--nldesign-component-notecard-info-color) calc((49.44 - l) * infinity) 0 0); + color: var(--nldesign-component-notecard-info-text-color, var(--thematiq-contrast-notecard-info-text-color, var(--color-main-text))) !important; +} + +body .notecard.notecard--warning, +body .notecard.warning { + --thematiq-contrast-notecard-warning-text-color: lch(from var(--nldesign-component-notecard-warning-color) calc((49.44 - l) * infinity) 0 0); + color: var(--nldesign-component-notecard-warning-text-color, var(--thematiq-contrast-notecard-warning-text-color, var(--color-main-text))) !important; +} + +body .notecard.notecard--error, +body .notecard.error { + --thematiq-contrast-notecard-error-text-color: lch(from var(--nldesign-component-notecard-error-color) calc((49.44 - l) * infinity) 0 0); + color: var(--nldesign-component-notecard-error-text-color, var(--thematiq-contrast-notecard-error-text-color, var(--color-main-text))) !important; +} + +body .notecard.notecard--success, +body .notecard.success { + --thematiq-contrast-notecard-success-text-color: lch(from var(--nldesign-component-notecard-success-color) calc((49.44 - l) * infinity) 0 0); + color: var(--nldesign-component-notecard-success-text-color, var(--thematiq-contrast-notecard-success-text-color, var(--color-main-text))) !important; +} + +/* badge-counter */ +.counter-bubble, +.counter-bubble__counter, +.app-navigation-entry__counter, +[data-thematiq-component='badge-counter'] { + --color-primary-element: var( + --nldesign-component-badge-background-color, + var(--thematiq-global-color-primary-element) + ); + --color-primary-element-text: var( + --nldesign-component-badge-color, + var(--thematiq-global-color-primary-element-text) + ); + --border-radius-pill: var( + --nldesign-component-badge-border-radius, + var(--thematiq-global-border-radius-pill) + ); + --color-favorite: var( + --nldesign-component-badge-favorite-color, + var(--thematiq-global-color-favorite) + ); +} + +/* toast */ +.toastify, +[class^='_toast_'], +[data-thematiq-component='toast'] { + --border-radius: var( + --nldesign-component-toast-border-radius, + var(--thematiq-global-border-radius) + ); +} + +body [class*='_toast_success_'], +body .toastify.toast-success { + background-color: var(--nldesign-component-toast-success-background-color, var(--color-main-background)); + --thematiq-contrast-toast-success-color: lch(from var(--nldesign-component-toast-success-background-color) calc((49.44 - l) * infinity) 0 0); + color: var(--nldesign-component-toast-success-color, var(--thematiq-contrast-toast-success-color, var(--color-main-text))); +} + +body [class*='_toast_error_'], +body .toastify.toast-error { + background-color: var(--nldesign-component-toast-error-background-color, var(--color-main-background)); + --thematiq-contrast-toast-error-color: lch(from var(--nldesign-component-toast-error-background-color) calc((49.44 - l) * infinity) 0 0); + color: var(--nldesign-component-toast-error-color, var(--thematiq-contrast-toast-error-color, var(--color-main-text))); +} + +body [class*='_toast_warning_'], +body .toastify.toast-warning { + background-color: var(--nldesign-component-toast-warning-background-color, var(--color-main-background)); + --thematiq-contrast-toast-warning-color: lch(from var(--nldesign-component-toast-warning-background-color) calc((49.44 - l) * infinity) 0 0); + color: var(--nldesign-component-toast-warning-color, var(--thematiq-contrast-toast-warning-color, var(--color-main-text))); +} + +body [class*='_toast_info_'], +body .toastify.toast-info { + background-color: var(--nldesign-component-toast-info-background-color, var(--color-main-background)); + --thematiq-contrast-toast-info-color: lch(from var(--nldesign-component-toast-info-background-color) calc((49.44 - l) * infinity) 0 0); + color: var(--nldesign-component-toast-info-color, var(--thematiq-contrast-toast-info-color, var(--color-main-text))); +} + +/* heading */ +h1, +h2, +h3, +h4, +h5, +h6, +[data-thematiq-component='heading'] { + --font-face: var( + --nldesign-component-heading-font-family, + var(--thematiq-global-font-face) + ); + --color-main-text: var( + --nldesign-component-heading-color, + var(--thematiq-global-color-main-text) + ); + --font-weight-heading: var( + --nldesign-component-heading-font-weight, + var(--thematiq-global-font-weight-heading) + ); +} + +/* paragraph */ +p, +[data-thematiq-component='paragraph'] { + --color-main-text: var( + --nldesign-component-paragraph-color, + var(--thematiq-global-color-main-text) + ); + --font-face: var( + --nldesign-component-paragraph-font-family, + var(--thematiq-global-font-face) + ); + --font-weight-default: var( + --nldesign-component-paragraph-font-weight, + var(--thematiq-global-font-weight-default) + ); +} + +/* link */ +a, +[data-thematiq-component='link'] { + --color-primary: var( + --nldesign-component-link-color, + var(--thematiq-global-color-primary) + ); + --color-primary-hover: var( + --nldesign-component-link-hover-color, + var(--thematiq-global-color-primary-hover) + ); +} + +/* muted-text */ +.settings-hint, +.app-settings-section__hint, +[data-thematiq-component='muted-text'] { + --color-text-maxcontrast: var( + --nldesign-component-text-muted-color, + var(--thematiq-global-color-text-maxcontrast) + ); + --color-text-light: var( + --nldesign-component-text-light-color, + var(--thematiq-global-color-text-light) + ); + --color-text-lighter: var( + --nldesign-component-text-lighter-color, + var(--thematiq-global-color-text-lighter) + ); +} + +/* status-text */ +.status-text, +.text-error, +.text-warning, +.text-success, +[data-thematiq-component='status-text'] { + --color-text-error: var( + --nldesign-component-text-error-color, + var(--thematiq-global-color-text-error) + ); + --color-text-warning: var( + --nldesign-component-text-warning-color, + var(--thematiq-global-color-text-warning) + ); + --color-text-success: var( + --nldesign-component-text-success-color, + var(--thematiq-global-color-text-success) + ); +} diff --git a/css/error-contrast.css b/css/error-contrast.css index 61c5d063..8d841e27 100644 --- a/css/error-contrast.css +++ b/css/error-contrast.css @@ -27,9 +27,14 @@ */ /* NcButton type="error" and NcChip variant="error" both render - `background: var(--color-error); color: var(--color-error-text)`. */ + `background: var(--color-error); color: var(--color-error-text)`. + The button reads its own label token first, so an admin who sets one — a + dark label on a theme whose error colour is pale — is not overruled here. */ .button-vue--vue-error, -.button-vue--error, +.button-vue--error { + color: var(--nldesign-component-button-error-color, #fff) !important; +} + .nc-chip--error { color: #fff !important; } diff --git a/css/playground.css b/css/playground.css index 0b8f8ed3..68aaed15 100644 --- a/css/playground.css +++ b/css/playground.css @@ -170,6 +170,16 @@ font-weight: 700; } +/* The rule between one variant's rows and the next in a component's panel. + Heavier and darker than the rule under each row, and with room around it, + so it reads as "a new group starts here" rather than as one more row edge. */ +.nldesign-pg-panel .nldesign-pg-rowsep { + height: 0; + margin: 18px 0 12px; + border: none; + border-top: 2px solid var(--color-border-maxcontrast); +} + .nldesign-pg-dim { color: var(--color-text-maxcontrast); font-weight: 400; @@ -288,6 +298,43 @@ cursor: text; } +/* The textarea as NcTextArea draws it: the typed text on its fill inside the + border, the placeholder while empty, the border darkening on focus and + taking the error edge when invalid. The fill, text, border, corner and + invalid edge are Nextcloud variables the textarea scope redirects; the + placeholder and the focused border have none, so they read their tokens + here, as css/component-scopes.css paints them on the real field. */ +#nldesign-preview .nldesign-pg-input.nldesign-pg-textarea { + align-items: flex-start; + width: 260px; + max-width: 100%; + min-height: 88px; + padding: 10px 12px; + border-color: var(--color-border-maxcontrast); + border-radius: var(--border-radius-element); + background: var(--color-main-background); + color: var(--color-main-text); +} + +#nldesign-preview .nldesign-pg-input.nldesign-pg-textarea:empty::before { + content: attr(data-placeholder); + color: var( + --nldesign-component-textarea-placeholder-color, + var(--color-text-maxcontrast) + ); +} + +#nldesign-preview .nldesign-pg-input.nldesign-pg-textarea:focus { + border-color: var( + --nldesign-component-textarea-focus-border-color, + var(--color-main-text) + ); +} + +#nldesign-preview .nldesign-pg-input.nldesign-pg-textarea.is-invalid { + border-color: var(--color-border-error, var(--color-error)); +} + #nldesign-preview .nldesign-pg-input[contenteditable]:focus-visible, #nldesign-preview .nldesign-pg-textarea[contenteditable]:focus-visible { outline: 2px solid var(--color-primary-element); @@ -521,8 +568,9 @@ /* The one state a drawing still has to hold still: `:active` lasts exactly as long as the press, and the cell exists to put the pressed colour beside the resting one. Core's own value for a tertiary button. */ +/* Held pressed: what NcButton's own `.button-vue:active` paints. */ #nldesign-preview .nldesign-pg-btn.is-active { - background-color: var(--color-primary-element-light-hover); + background-color: var(--color-primary-element-light); } /* The floor, and ONLY when this page turned out not to carry NcButton's @@ -576,8 +624,8 @@ } #nldesign-preview .nldesign-pg-unscoped .button-vue--vue-secondary { - background: var(--color-background-dark); - color: var(--color-main-text); + background: var(--color-primary-element-light); + color: var(--color-primary-element-light-text); } #nldesign-preview .nldesign-pg-unscoped .button-vue--tertiary { @@ -626,8 +674,11 @@ height: 50px; margin: -16px; padding: 0 10px; - background: transparent; - color: var(--color-primary-text); + /* Painted the way css/component-scopes.css paints the real `#header`: from + the header token, or not at all — Nextcloud gives the bar no background + of its own, so the ground behind it shows through, as the page does. */ + background-color: var(--nldesign-component-header-background-color, transparent); + color: var(--nldesign-component-header-color, var(--color-primary-text)); } /* The logo the instance is actually wearing, resolved through the same chain @@ -689,6 +740,7 @@ /* A hovered entry takes the light content-surface hover colour, so its white icon has to invert to stay visible — which is exactly what core does, and what a set with a dark hover colour breaks. */ +#nldesign-preview .nldesign-pg-header .app-menu-entry:hover .nldesign-pg-icon, #nldesign-preview .nldesign-pg-header .app-menu-entry.is-hover .nldesign-pg-icon { filter: invert(100%); } @@ -708,6 +760,7 @@ colour, so a light box appears on the saturated bar and the glyph inside it has to flip to the body text colour to stay legible. That flip is the point — a set whose hover colour is dark makes this unreadable. */ +#nldesign-preview .nldesign-pg-header .app-menu-entry:hover, #nldesign-preview .nldesign-pg-header .app-menu-entry.is-hover { background: var(--color-background-hover); } @@ -799,10 +852,17 @@ margin-block: 0 auto; } -/* Login.vue: `.login-box { width: 320px }` — "same size as dashboard panels". */ +/* Login.vue: `.login-box { width: 320px }` — "same size as dashboard panels". + + The card's edge, painted from its token the way css/component-scopes.css + paints the real login box: Nextcloud's guest box draws no border, so there + is no variable to redirect. Unset, the declaration is invalid and the card + has no edge, as in Nextcloud. `!important` because the nldesign theme sets + `border: none !important` on every guest box. */ #nldesign-preview .nldesign-pg-logincard { width: 320px; max-width: 100%; + border: 1px solid var(--nldesign-component-login-box-border-color) !important; } /* Login.vue: the wrapper stacks with a two-baseline gap. */ @@ -943,6 +1003,7 @@ `:not(.app-navigation-entry--legacy)` rule after it wins on every entry that is not explicitly legacy. (Selected-and-hovered is 22%, and has no rule here because the stage draws the two states separately.) */ +#nldesign-preview .nldesign-pg-nav-entry:not(.is-selected):hover, #nldesign-preview .nldesign-pg-nav-entry.is-hover { background: color-mix(in srgb, var(--color-primary-element) 8%, transparent); } @@ -1000,12 +1061,19 @@ font-size: 14px; } +/* The header row's fill and weight have no Nextcloud variable to redirect, so + they are painted from their tokens here, as css/component-scopes.css paints + them on real tables, falling back to what Nextcloud draws. */ #nldesign-preview .nldesign-pg-table th { padding: 10px 12px; border-bottom: 1px solid var(--color-border); + background-color: var( + --nldesign-component-table-header-background-color, + transparent + ); color: var(--color-text-maxcontrast); font-size: 13px; - font-weight: 400; + font-weight: var(--nldesign-component-table-header-font-weight, 400); text-align: left; } @@ -1031,6 +1099,7 @@ background: var(--color-background-dark); } +#nldesign-preview .nldesign-pg-table tbody tr:hover td, #nldesign-preview .nldesign-pg-table tr.is-hover td { background: var(--color-background-hover); } @@ -1105,6 +1174,7 @@ transition: background-color var(--animation-quick, 100ms); } +#nldesign-preview .nldesign-pg-listitem:not(.is-selected):hover, #nldesign-preview .nldesign-pg-listitem.is-hover { background: var(--color-background-hover); } @@ -1318,6 +1388,7 @@ white-space: nowrap; } +#nldesign-preview .nldesign-pg-action:hover .action-button, #nldesign-preview .nldesign-pg-action.is-hover .action-button, #nldesign-preview .nldesign-pg-popover .action-button:hover { background: var(--color-background-hover); @@ -1338,6 +1409,7 @@ color: var(--color-primary); } +#nldesign-preview .nldesign-pg-crumbs .nldesign-pg-crumb:not(.is-current):hover, #nldesign-preview .nldesign-pg-crumbs .nldesign-pg-crumb.is-hover { background: var(--color-background-hover); } @@ -1509,9 +1581,21 @@ max-width: 100%; } +/* NcSelect is vue-select, and it is themed through vue-select's own `--vs-*` + variables, which the select scope in css/component-scopes.css points at the + select tokens. The Nextcloud colour behind each is what NcSelect itself + sets, for a page that does not carry NcSelect's stylesheet. */ #nldesign-preview .nldesign-pg-select { justify-content: space-between; - color: var(--color-main-text); + border-color: var(--vs-border-color, var(--color-border-maxcontrast)); + border-radius: var(--vs-border-radius, var(--border-radius-element)); + color: var(--vs-selected-color, var(--color-main-text)); +} + +#nldesign-preview .nldesign-pg-selected { + padding: 2px 10px; + border-radius: 16px; + background: var(--color-primary-element-light); } #nldesign-preview .nldesign-pg-options { @@ -1530,11 +1614,17 @@ min-height: 36px; padding: 0 10px; border-radius: var(--border-radius); + color: var(--vs-dropdown-color, var(--color-main-text)); font-size: 14px; } +#nldesign-preview .nldesign-pg-option:hover, #nldesign-preview .nldesign-pg-option.is-hover { - background: var(--color-background-hover); + background: var(--vs-dropdown-option--active-bg, var(--color-background-hover)); + color: var( + --vs-dropdown-option--active-color, + var(--vs-dropdown-color, var(--color-main-text)) + ); } /* Checkbox, radio, switch ------------------------------------------------------------ */ @@ -1687,30 +1777,90 @@ background: currentcolor; } +/* A note card as Nextcloud 34+ draws one (NcNoteCard): the type colour is the + FILL, its paired text colour the stripe and the glyph. The text colour is not + set here — css/component-scopes.css paints it, with !important as core does, + from the card's text token, or white/black against a background the theme + set, or the main text. */ #nldesign-preview .nldesign-pg-note.is-info { - border-left: 4px solid var(--color-info); - background: var(--color-background-hover); - color: var(--color-element-info); + border-inline-start: 4px solid var(--color-info-text, var(--color-element-info)); + background: var(--color-info); } #nldesign-preview .nldesign-pg-note.is-warning { - border-left: 4px solid var(--color-warning); - background: rgb(var(--color-warning-rgb) / 10%); - color: var(--color-element-warning); + border-inline-start: 4px solid + var(--color-warning-text, var(--color-element-warning)); + background: var(--color-warning); } #nldesign-preview .nldesign-pg-note.is-error { - border: 1px solid var(--color-border-error); - border-left: 4px solid var(--color-error); - background: rgb(var(--color-error-rgb) / 10%); - color: var(--color-element-error); + border-inline-start: 4px solid + var(--color-error-text, var(--color-element-error)); + background: var(--color-error); } #nldesign-preview .nldesign-pg-note.is-success { - border: 1px solid var(--color-border-success); - border-left: 4px solid var(--color-success); - background: rgb(var(--color-success-rgb) / 10%); - color: var(--color-element-success); + border-inline-start: 4px solid + var(--color-success-text, var(--color-element-success)); + background: var(--color-success); +} + +/* Nextcloud 32 (@nextcloud/vue 8): the fill is the type colour's `-rgb` + triplet at 10%, and the stripe and glyph are the type colour itself. + Declared as the variables NcNoteCard reads rather than painted: the + specimen carries the real component's scope, and its own rule paints the + fill from --note-background with !important, as it does on the page. */ +#nldesign-preview .nldesign-pg-note.is-v32.is-info { + --note-background: rgba(var(--color-info-rgb), 0.1); + --note-theme: var(--color-info); +} + +#nldesign-preview .nldesign-pg-note.is-v32.is-warning { + --note-background: rgba(var(--color-warning-rgb), 0.1); + --note-theme: var(--color-warning); +} + +#nldesign-preview .nldesign-pg-note.is-v32.is-error { + --note-background: rgba(var(--color-error-rgb), 0.1); + --note-theme: var(--color-error); +} + +#nldesign-preview .nldesign-pg-note.is-v32.is-success { + --note-background: rgba(var(--color-success-rgb), 0.1); + --note-theme: var(--color-success); +} + +/* The glyph takes the stripe colour, as NcNoteCard's does. */ +#nldesign-preview .nldesign-pg-note.is-info .nldesign-pg-note-icon { + color: var(--color-info-text, var(--color-element-info)); +} + +#nldesign-preview .nldesign-pg-note.is-warning .nldesign-pg-note-icon { + color: var(--color-warning-text, var(--color-element-warning)); +} + +#nldesign-preview .nldesign-pg-note.is-error .nldesign-pg-note-icon { + color: var(--color-error-text, var(--color-element-error)); +} + +#nldesign-preview .nldesign-pg-note.is-success .nldesign-pg-note-icon { + color: var(--color-success-text, var(--color-element-success)); +} + +#nldesign-preview .nldesign-pg-note.is-v32.is-info .nldesign-pg-note-icon { + color: var(--color-info); +} + +#nldesign-preview .nldesign-pg-note.is-v32.is-warning .nldesign-pg-note-icon { + color: var(--color-warning); +} + +#nldesign-preview .nldesign-pg-note.is-v32.is-error .nldesign-pg-note-icon { + color: var(--color-error); +} + +#nldesign-preview .nldesign-pg-note.is-v32.is-success .nldesign-pg-note-icon { + color: var(--color-success); } /* Badges and toasts ------------------------------------------------------------------------------ */ @@ -1734,30 +1884,40 @@ font-size: 24px; } +/* A toast as Nextcloud draws one: a card on the main background, main text, + and the type colour as a stripe. The background and the text are NOT set + here: css/component-scopes.css paints both from the toast tokens, on the real + toasts and on this specimen alike, and a rule under the preview's id would + out-rank it and freeze the specimen. The text follows the background when a + theme sets one and no text colour of its own. */ #nldesign-preview .nldesign-pg-toast { + position: static; display: block; max-width: 380px; margin-bottom: 8px; padding: 12px 14px; - border-radius: var(--border-radius); - color: #fff; + border-radius: var( + --nldesign-component-toast-border-radius, + var(--border-radius) + ); + box-shadow: 0 0 6px 0 var(--color-box-shadow); font-size: 14px; } #nldesign-preview .nldesign-pg-toast.is-success { - background: var(--color-success); + border-inline-start: 3px solid var(--color-element-success, var(--color-success)); } #nldesign-preview .nldesign-pg-toast.is-error { - background: var(--color-error); + border-inline-start: 3px solid var(--color-element-error, var(--color-error)); } #nldesign-preview .nldesign-pg-toast.is-warning { - background: var(--color-warning); + border-inline-start: 3px solid var(--color-element-warning, var(--color-warning)); } #nldesign-preview .nldesign-pg-toast.is-info { - background: var(--color-info); + border-inline-start: 3px solid var(--color-element-info, var(--color-primary)); } /* Type ------------------------------------------------------------------------------------------ */ @@ -1809,6 +1969,10 @@ text-decoration: underline; } +/* Hovered, or focused from the keyboard: the link's hover colour, which the + link scope in css/component-scopes.css points at the link hover token. */ +#nldesign-preview .nldesign-pg-link:hover, +#nldesign-preview .nldesign-pg-link:focus-visible, #nldesign-preview .nldesign-pg-link.is-hover { color: var(--color-primary-hover); } @@ -1864,6 +2028,7 @@ @media (prefers-reduced-motion: reduce) { #nldesign-preview .nldesign-pg-dialog, #nldesign-preview .nldesign-pg-listitem, + #nldesign-preview .nldesign-pg-searchbtn, #nldesign-preview .nldesign-pg-spinner { transition: none !important; animation: none !important; @@ -1950,7 +2115,10 @@ min-width: var(--default-clickable-area, 34px); height: var(--default-clickable-area, 34px); border-radius: var(--border-radius-element, 8px); - color: var(--nldesign-color-header-text, var(--color-primary-text)); + color: var( + --nldesign-component-header-color, + var(--nldesign-color-header-text, var(--color-primary-text)) + ); } #nldesign-preview .nldesign-pg-currentapp { @@ -2027,9 +2195,14 @@ other one does something else. */ #nldesign-preview .nldesign-pg-header.is-modern { position: relative; + /* The header token first, as element-overrides.css and the component + scope both put it on the live header. */ color: var( - --nldesign-color-header-text, - var(--color-background-plain-text, var(--color-primary-text)) + --nldesign-component-header-color, + var( + --nldesign-color-header-text, + var(--color-background-plain-text, var(--color-primary-text)) + ) ); } diff --git a/css/primary-lock.css b/css/primary-lock.css new file mode 100644 index 00000000..33da2464 --- /dev/null +++ b/css/primary-lock.css @@ -0,0 +1,72 @@ +/** + * GENERATED FILE — do not edit. + * + * SPDX-License-Identifier: EUPL-1.2 + * SPDX-FileCopyrightText: 2026 Conduction B.V. + * + * Source: scripts/mapping/component-tokens.json + * Regenerate: npm run generate:component-scopes + */ + +/* + * The `primary_drives_components` toggle, as a stylesheet. + * + * Emitted by CssInjectionService only while the admin setting is on, and + * emitted AFTER custom-overrides.css so it outranks any per-component value + * already stored there. Nothing is deleted: turning the setting back off + * drops this layer and the stored values take effect again. + * + * Only the tokens flagged `primary` in the mapping appear here — the ones the + * brand primary used to drive before components could be themed separately. + * A component token for a border radius or a font weight is untouched, and + * stays editable while the toggle is on. + * + * On `body` for the same reason the capture block is: these declarations read + * `--thematiq-global-*`, which is declared there. Landing on `body` also means + * they beat custom-overrides.css's `:root` `!important` for every descendant, + * which is exactly what the toggle is for. + */ + +body { + /* header-bar */ + --nldesign-component-header-background-color: var(--thematiq-global-color-background-plain) !important; + --nldesign-component-header-color: var(--thematiq-global-color-background-plain-text) !important; + /* login-button */ + --nldesign-component-login-button-background-color: var(--thematiq-global-color-primary-element) !important; + --nldesign-component-login-button-color: var(--thematiq-global-color-primary-element-text) !important; + --nldesign-component-login-button-hover-background-color: var(--thematiq-global-color-primary-element-hover) !important; + /* logo-slogan */ + --nldesign-component-logo-slogan-color: var(--thematiq-global-color-primary-text) !important; + /* app-navigation */ + --nldesign-component-navigation-active-background-color: var(--thematiq-global-color-primary-element) !important; + /* sidebar */ + --nldesign-component-sidebar-active-color: var(--thematiq-global-color-primary-element) !important; + /* list-item */ + --nldesign-component-list-item-selected-background-color: var(--thematiq-global-color-primary-light) !important; + --nldesign-component-list-item-selected-color: var(--thematiq-global-color-primary-light-text) !important; + --nldesign-component-list-item-selected-hover-background-color: var(--thematiq-global-color-primary-light-hover) !important; + /* breadcrumbs */ + --nldesign-component-breadcrumb-color: var(--thematiq-global-color-primary) !important; + /* text-input */ + --nldesign-component-textbox-focus-border-color: var(--thematiq-global-color-primary-element) !important; + /* checkbox-switch */ + --nldesign-component-checkbox-checked-background-color: var(--thematiq-global-color-primary-element) !important; + /* dialog */ + --nldesign-component-dialog-confirm-background-color: var(--thematiq-global-color-primary-element) !important; + /* progress */ + --nldesign-component-progress-background-color: var(--thematiq-global-color-primary-element) !important; + /* primary-button */ + --nldesign-component-button-primary-action-background-color: var(--thematiq-global-color-primary-element) !important; + --nldesign-component-button-primary-action-color: var(--thematiq-global-color-primary-element-text) !important; + --nldesign-component-button-primary-action-hover-background-color: var(--thematiq-global-color-primary-element-hover) !important; + /* tertiary-button */ + --nldesign-component-button-color: var(--thematiq-global-color-main-text) !important; + --nldesign-component-button-hover-background-color: var(--thematiq-global-color-primary-element-light) !important; + --nldesign-component-button-active-background-color: var(--thematiq-global-color-primary-element-light) !important; + /* badge-counter */ + --nldesign-component-badge-background-color: var(--thematiq-global-color-primary-element) !important; + --nldesign-component-badge-color: var(--thematiq-global-color-primary-element-text) !important; + /* link */ + --nldesign-component-link-color: var(--thematiq-global-color-primary) !important; + --nldesign-component-link-hover-color: var(--thematiq-global-color-primary-hover) !important; +} diff --git a/css/systems/nldesign/defaults.css b/css/systems/nldesign/defaults.css index 718e91b1..1af5fc01 100644 --- a/css/systems/nldesign/defaults.css +++ b/css/systems/nldesign/defaults.css @@ -318,41 +318,63 @@ * COMPONENT TOKENS — Typography: Headings * =================================================================== */ + /* ALL headings, which is the level the playground's `Headings` chip edits. + * The six per-level tokens below derive from these three rather than from + * the brand tokens directly, so moving the heading colour or weight once + * moves h1 through h6 — while a token set that wants one level to differ + * still overrides that level's own token and wins, because its declaration + * replaces the derivation. */ + --nldesign-component-heading-font-family: var(--nldesign-font-family); + --nldesign-component-heading-font-weight: var(--nldesign-font-weight-heading); + --nldesign-component-heading-color: var(--nldesign-color-text); + /* Heading 1 */ --nldesign-component-heading-1-font-size: 2.5rem; - --nldesign-component-heading-1-font-weight: var(--nldesign-font-weight-heading); + --nldesign-component-heading-1-font-weight: var( + --nldesign-component-heading-font-weight + ); --nldesign-component-heading-1-line-height: 1.2; - --nldesign-component-heading-1-color: var(--nldesign-color-text); + --nldesign-component-heading-1-color: var(--nldesign-component-heading-color); /* Heading 2 */ --nldesign-component-heading-2-font-size: 2rem; - --nldesign-component-heading-2-font-weight: var(--nldesign-font-weight-heading); + --nldesign-component-heading-2-font-weight: var( + --nldesign-component-heading-font-weight + ); --nldesign-component-heading-2-line-height: 1.25; - --nldesign-component-heading-2-color: var(--nldesign-color-text); + --nldesign-component-heading-2-color: var(--nldesign-component-heading-color); /* Heading 3 */ --nldesign-component-heading-3-font-size: 1.5rem; - --nldesign-component-heading-3-font-weight: var(--nldesign-font-weight-heading); + --nldesign-component-heading-3-font-weight: var( + --nldesign-component-heading-font-weight + ); --nldesign-component-heading-3-line-height: 1.3; - --nldesign-component-heading-3-color: var(--nldesign-color-text); + --nldesign-component-heading-3-color: var(--nldesign-component-heading-color); /* Heading 4 */ --nldesign-component-heading-4-font-size: 1.25rem; - --nldesign-component-heading-4-font-weight: var(--nldesign-font-weight-heading); + --nldesign-component-heading-4-font-weight: var( + --nldesign-component-heading-font-weight + ); --nldesign-component-heading-4-line-height: 1.35; - --nldesign-component-heading-4-color: var(--nldesign-color-text); + --nldesign-component-heading-4-color: var(--nldesign-component-heading-color); /* Heading 5 */ --nldesign-component-heading-5-font-size: 1.125rem; - --nldesign-component-heading-5-font-weight: var(--nldesign-font-weight-heading); + --nldesign-component-heading-5-font-weight: var( + --nldesign-component-heading-font-weight + ); --nldesign-component-heading-5-line-height: 1.4; - --nldesign-component-heading-5-color: var(--nldesign-color-text); + --nldesign-component-heading-5-color: var(--nldesign-component-heading-color); /* Heading 6 */ --nldesign-component-heading-6-font-size: 1rem; - --nldesign-component-heading-6-font-weight: var(--nldesign-font-weight-heading); + --nldesign-component-heading-6-font-weight: var( + --nldesign-component-heading-font-weight + ); --nldesign-component-heading-6-line-height: 1.5; - --nldesign-component-heading-6-color: var(--nldesign-color-text); + --nldesign-component-heading-6-color: var(--nldesign-component-heading-color); /* =================================================================== * COMPONENT TOKENS — Typography: Paragraph @@ -361,6 +383,8 @@ --nldesign-component-paragraph-font-size: 1rem; --nldesign-component-paragraph-line-height: 1.5; --nldesign-component-paragraph-color: var(--nldesign-color-text); + --nldesign-component-paragraph-font-family: var(--nldesign-font-family); + --nldesign-component-paragraph-font-weight: var(--nldesign-font-weight-default); /* =================================================================== * COMPONENT TOKENS — Link diff --git a/css/systems/nldesign/element-overrides.css b/css/systems/nldesign/element-overrides.css index cb0aa61b..dab23c99 100644 --- a/css/systems/nldesign/element-overrides.css +++ b/css/systems/nldesign/element-overrides.css @@ -154,7 +154,10 @@ header#header .header-menu__trigger { #header .header-start svg, #header .app-menu__waffle, #header .app-menu__waffle svg { - color: var(--nldesign-color-header-text) !important; + color: var( + --nldesign-component-header-color, + var(--nldesign-color-header-text) + ) !important; filter: none !important; -webkit-filter: none !important; opacity: 1 !important; @@ -487,6 +490,20 @@ a:not(#header a):not(.tile-widget a):not(.tile-widget * a) { --nldesign-color-on-surface: var(--color-primary-text, #ffffff); } +/* + * The secondary button paints its label on the button itself (theme.css), but + * the label is a `span` inside it, and the blanket rule above claims every span + * except a primary button's. So the label wore the body text colour and the + * secondary button's label token — saved or previewed — never showed. Its own + * token goes on the surface, and the spans take it from there. + */ +.button-vue--vue-secondary, +.button-vue--secondary { + --nldesign-color-on-surface: var( + --nldesign-component-button-secondary-action-color + ); +} + /* * The NcNoteCard fill is NOT fixed here — every design system that reads * `--nldesign-*` inverts the same contract, so it lives in the shared @@ -529,7 +546,17 @@ button[class*='primary']:not([data-admin-theming-setting-color-picker]), button[class*='primary']:not([data-admin-theming-setting-color-picker]) *, .button-vue[class*='primary']:not([data-admin-theming-setting-color-picker]), .button-vue[class*='primary']:not([data-admin-theming-setting-color-picker]) * { - color: var(--nldesign-color-primary-text) !important; + /* The component token first, the brand token behind it. + * + * This rule claims the label AND every descendant of a primary button, at a + * specificity nothing in the scope layer can outrank — so while it read the + * BRAND token directly, no per-button label token could ever take effect. + * That is why the login button's label stayed white: its own token was + * never in the chain that painted it. */ + color: var( + --nldesign-component-button-primary-action-color, + var(--nldesign-color-primary-text) + ) !important; } /* ============================================ @@ -594,16 +621,57 @@ button[class*='primary']:not([data-admin-theming-setting-color-picker]) *, BORDER RADIUS CONSISTENCY ============================================ */ +/* Split by component, and this file is the one that decides. + * + * theme.css carries the same list under "REMOVE ALL ROUNDED CORNERS", but this + * file loads AFTER it, so whatever is written here is what a component ends up + * with. While both were one blanket rule, splitting theme.css alone changed + * nothing — every Corner row in the playground still set a token this rule + * overrode. Each token below is undeclared until an admin sets one and falls + * back to `--nldesign-border-radius`, so a sharp theme is unaffected. */ .button, button:not(.action-button), -.button-vue:not(.action-button), -input, -textarea, -select, +.button-vue:not(.action-button) { + border-radius: var( + --nldesign-component-button-border-radius, + var(--nldesign-border-radius) + ) !important; +} + +input { + border-radius: var( + --nldesign-component-textbox-border-radius, + var(--nldesign-border-radius) + ) !important; +} + +textarea { + border-radius: var( + --nldesign-component-textarea-border-radius, + var(--nldesign-border-radius) + ) !important; +} + +select { + border-radius: var( + --nldesign-component-form-select-border-radius, + var(--nldesign-border-radius) + ) !important; +} + .dropdown, -.modal-container, .popover { - border-radius: var(--nldesign-border-radius) !important; + border-radius: var( + --nldesign-component-actions-menu-border-radius, + var(--nldesign-border-radius) + ) !important; +} + +.modal-container { + border-radius: var( + --nldesign-component-dialog-border-radius, + var(--nldesign-border-radius) + ) !important; } /* @@ -659,10 +727,19 @@ body { * for absolutely positioned descendants. `overflow: visible` stays — the ribbon * is allowed to hang below the header when its height exceeds the header's. */ +/* The component token wins, then the set's own header colour, then white. The + chain is what lets the header be moved from the playground without touching + `--nldesign-color-header-background`, which a token set owns. */ #header { - background-color: var(--nldesign-color-header-background, #ffffff) !important; + background-color: var( + --nldesign-component-header-background-color, + var(--nldesign-color-header-background, #ffffff) + ) !important; background-image: none !important; - background: var(--nldesign-color-header-background, #ffffff) !important; + background: var( + --nldesign-component-header-background-color, + var(--nldesign-color-header-background, #ffffff) + ) !important; border-bottom: var(--nldesign-header-border-bottom, 0) !important; overflow: visible !important; } @@ -693,7 +770,10 @@ body { } #header nav.app-menu .app-menu-entry__label { - color: var(--nldesign-color-header-text) !important; + color: var( + --nldesign-component-header-color, + var(--nldesign-color-header-text) + ) !important; font-weight: 400 !important; } @@ -734,7 +814,10 @@ body { * and the extra bar is gone. */ #header nav.app-menu .app-menu-entry--active::before, #header nav.app-menu .app-menu-entry--active a::before { - background-color: var(--nldesign-color-header-text) !important; + background-color: var( + --nldesign-component-header-color, + var(--nldesign-color-header-text) + ) !important; } #header nav.app-menu .app-menu-entry { @@ -758,10 +841,14 @@ body { color: var(--color-primary) !important; } -/* Header branding text */ +/* Header branding text — follows the header's own colour token when one is + set, and keeps the brand primary when none is. */ #header .header-appname, #header .header-left a { - color: var(--nldesign-color-primary) !important; + color: var( + --nldesign-component-header-color, + var(--nldesign-color-primary) + ) !important; font-weight: 600 !important; } @@ -793,7 +880,10 @@ body { #header .header-right a, #header .header-right button, #header .menutoggle { - color: var(--nldesign-color-header-text) !important; + color: var( + --nldesign-component-header-color, + var(--nldesign-color-header-text) + ) !important; opacity: 1 !important; } @@ -802,7 +892,10 @@ body { so they keep their own painting. */ #header .header-right [class^='icon-'], #header .header-right svg:not([class*='avatar' i] svg):not(.user-status-icon svg) { - color: var(--nldesign-color-header-text) !important; + color: var( + --nldesign-component-header-color, + var(--nldesign-color-header-text) + ) !important; fill: currentColor !important; filter: none !important; opacity: 1 !important; @@ -810,7 +903,10 @@ body { /* Unified search */ #header .unified-search__button { - color: var(--nldesign-color-header-text) !important; + color: var( + --nldesign-component-header-color, + var(--nldesign-color-header-text) + ) !important; filter: none !important; } diff --git a/css/systems/nldesign/theme.css b/css/systems/nldesign/theme.css index fb5d0bb4..029680f1 100644 --- a/css/systems/nldesign/theme.css +++ b/css/systems/nldesign/theme.css @@ -67,8 +67,14 @@ body { /* Main header bar */ #header, header#header { - background: var(--nldesign-color-header-background) !important; - background-color: var(--nldesign-color-header-background) !important; + background: var( + --nldesign-component-header-background-color, + var(--nldesign-color-header-background) + ) !important; + background-color: var( + --nldesign-component-header-background-color, + var(--nldesign-color-header-background) + ) !important; overflow: visible !important; } @@ -147,7 +153,10 @@ header#header { #header *, #header .header-appname, #header .header-appname-container .header-appname { - color: var(--nldesign-color-header-text) !important; + color: var( + --nldesign-component-header-color, + var(--nldesign-color-header-text) + ) !important; } /* Header app icons — the app menu's icons are IMAGES (white SVG files), so @@ -165,7 +174,10 @@ header#header { /* User menu in header */ #header .header-menu, #header #user-menu { - color: var(--nldesign-color-header-text) !important; + color: var( + --nldesign-component-header-color, + var(--nldesign-color-header-text) + ) !important; } /* ============================================ @@ -183,7 +195,10 @@ header#header { max-width: 500px; margin: 0 auto; box-shadow: none !important; - border-radius: var(--nldesign-border-radius) !important; + border-radius: var( + --nldesign-component-login-box-border-radius, + var(--nldesign-border-radius) + ) !important; padding-top: 0 !important; /* Remove top padding so header is at top of box */ } @@ -203,7 +218,10 @@ header#header { #body-login .body-login-container > div { background: #ffffff !important; box-shadow: none !important; - border-radius: var(--nldesign-border-radius) !important; + border-radius: var( + --nldesign-component-login-box-border-radius, + var(--nldesign-border-radius) + ) !important; border: none !important; } @@ -215,7 +233,10 @@ header#header { background: #ffffff !important; background-color: #ffffff !important; box-shadow: none !important; - border-radius: var(--nldesign-border-radius) !important; + border-radius: var( + --nldesign-component-login-box-border-radius, + var(--nldesign-border-radius) + ) !important; border: none !important; backdrop-filter: none !important; -webkit-backdrop-filter: none !important; @@ -320,14 +341,30 @@ div[class*='login-box__wrapper'] > div { /* Remove all shadows from login elements */ #body-login input, +/* The login page's own shape reset. The button reads its OWN corner token + here, otherwise this rule outranks the button rule below and the Login + button chip's Corner row does nothing. */ #body-login button, #body-login .button-vue, #body-login a { box-shadow: none !important; - border-radius: var(--nldesign-border-radius) !important; + border-radius: var( + --nldesign-component-login-button-border-radius, + var(--nldesign-border-radius) + ) !important; } -/* Login box primary button */ +/* Login box primary button. + * + * The login button reads its OWN component tokens first and falls back to the + * shared primary-action ones. Without that fallback chain this rule painted the + * login button from `--nldesign-component-button-primary-action-*` — the same + * tokens the main primary button uses — so the two could not be moved apart, + * which is exactly what `--nldesign-component-login-button-*` exists to allow. + * + * The fallback is what keeps every shipped token set correct: a set that styles + * its buttons and says nothing about the login page still gets the button + * colours here, as it did before. */ #body-login .button-vue--vue-primary, #body-login .button-vue--primary, #body-login input[type='submit'], @@ -342,7 +379,7 @@ div[class*='login-box__wrapper'] > div { color: var(--nldesign-component-button-primary-action-color) !important; border-radius: var(--nldesign-component-button-border-radius) !important; box-shadow: none !important; - transition: background-color var(--nldesign-animation-quick) ease !important; + transition: background-color var(--animation-quick) ease !important; } #body-login .button-vue--vue-primary:hover, @@ -425,9 +462,9 @@ button:not(.action-button), border-radius: var(--nldesign-component-button-border-radius) !important; border-width: var(--nldesign-component-button-border-width) !important; transition: - background-color var(--nldesign-animation-quick) ease, - border-color var(--nldesign-animation-quick) ease, - color var(--nldesign-animation-quick) ease !important; + background-color var(--animation-quick) ease, + border-color var(--animation-quick) ease, + color var(--animation-quick) ease !important; } /* Primary buttons */ @@ -571,7 +608,7 @@ button.secondary:hover { a { color: var(--nldesign-component-link-color) !important; text-decoration: var(--nldesign-component-link-text-decoration) !important; - transition: color var(--nldesign-animation-quick) ease !important; + transition: color var(--animation-quick) ease !important; } a:hover { @@ -586,7 +623,10 @@ a:visited { #header a, #header a:hover, #header a:visited { - color: var(--nldesign-color-header-text) !important; + color: var( + --nldesign-component-header-color, + var(--nldesign-color-header-text) + ) !important; text-decoration: none !important; } @@ -620,8 +660,8 @@ textarea { font-family: var(--nldesign-component-textbox-font-family) !important; font-size: var(--nldesign-component-textbox-font-size) !important; transition: - border-color var(--nldesign-animation-quick) ease, - box-shadow var(--nldesign-animation-quick) ease !important; + border-color var(--animation-quick) ease, + box-shadow var(--animation-quick) ease !important; } input[type='text']:hover, @@ -665,7 +705,7 @@ textarea:disabled { select { border-color: var(--nldesign-component-form-select-border-color) !important; border-radius: var(--nldesign-component-form-select-border-radius) !important; - transition: border-color var(--nldesign-animation-quick) ease !important; + transition: border-color var(--animation-quick) ease !important; } select:focus { @@ -685,10 +725,19 @@ label, CHECKBOXES & RADIO BUTTONS ============================================ */ +/* The checkbox reads its own token first so it can be moved without moving the + brand primary, and falls back to the primary so every existing token set is + unaffected. */ input[type='checkbox']:checked, input[type='radio']:checked { - background-color: var(--nldesign-color-primary) !important; - border-color: var(--nldesign-color-primary) !important; + background-color: var( + --nldesign-component-checkbox-checked-background-color, + var(--nldesign-color-primary) + ) !important; + border-color: var( + --nldesign-component-checkbox-checked-background-color, + var(--nldesign-color-primary) + ) !important; } /* ============================================ @@ -697,7 +746,10 @@ input[type='radio']:checked { .icon-loading, .icon-loading-small { - border-color: var(--nldesign-color-primary) !important; + border-color: var( + --nldesign-component-progress-background-color, + var(--nldesign-color-primary) + ) !important; } /* ============================================ @@ -747,11 +799,25 @@ h6 { color: var(--nldesign-component-heading-6-color) !important; } +/* Typeface for headings, as one rule rather than six: the six per-level tokens + cover size, weight, line-height and colour, and a brand that gives its + headings a display face gives it to all of them. */ +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: var(--nldesign-component-heading-font-family) !important; +} + /* Paragraphs */ p { font-size: var(--nldesign-component-paragraph-font-size) !important; line-height: var(--nldesign-component-paragraph-line-height) !important; color: var(--nldesign-component-paragraph-color) !important; + font-family: var(--nldesign-component-paragraph-font-family) !important; + font-weight: var(--nldesign-component-paragraph-font-weight) !important; } /* Tables */ @@ -785,7 +851,10 @@ hr { ============================================ */ .toastify.dialogs { - border-radius: var(--nldesign-border-radius) !important; + border-radius: var( + --nldesign-component-toast-border-radius, + var(--nldesign-border-radius) + ) !important; } /* ============================================ @@ -931,22 +1000,68 @@ button:not(.action-button), * instance therefore rendered every avatar — the user menu in the header, * the contacts menu, share recipients — as a 3px-cornered SQUARE, which is not * a brand choice any token set made. File previews and app icons were squared - * off the same way. Components that need the brand radius are listed by name. */ + * off the same way. Components that need the brand radius are listed by name. + * + * SPLIT BY COMPONENT, and that is what makes the corner rows work. + * + * This was one rule forcing `--nldesign-border-radius` onto every selector + * below, which outranked each component's own radius rule — so the Corner row + * on seven chips set a token nothing read. Every component token here is + * UNDECLARED until someone sets one and falls back to `--nldesign-border-radius`, + * so a sharp theme still squares off exactly as before. */ .panel, .widget, -.button-vue:not(.action-button), -button:not(.action-button), -.modal-container, -.app-navigation-entry, -input, -textarea, -select, -.dropdown, -.popover, .tooltip { border-radius: var(--nldesign-border-radius) !important; } +.button-vue:not(.action-button), +button:not(.action-button) { + border-radius: var( + --nldesign-component-button-border-radius, + var(--nldesign-border-radius) + ) !important; +} + +.modal-container { + border-radius: var( + --nldesign-component-dialog-border-radius, + var(--nldesign-border-radius) + ) !important; +} + +.app-navigation-entry { + border-radius: var( + --nldesign-component-navigation-border-radius, + var(--nldesign-border-radius) + ) !important; +} + +input { + border-radius: var( + --nldesign-component-textbox-border-radius, + var(--nldesign-border-radius) + ) !important; +} + +textarea { + border-radius: var( + --nldesign-component-textarea-border-radius, + var(--nldesign-border-radius) + ) !important; +} + +/* `select` is not repeated here: its own rule in FORM ELEMENTS above already + reads `--nldesign-component-form-select-border-radius`. */ + +.dropdown, +.popover { + border-radius: var( + --nldesign-component-actions-menu-border-radius, + var(--nldesign-border-radius) + ) !important; +} + /* ============================================ FONT APPLICATION ============================================ */ diff --git a/css/tokens/dark/amsterdam.css b/css/tokens/dark/amsterdam.css index f18b2d80..c8e56bb2 100644 --- a/css/tokens/dark/amsterdam.css +++ b/css/tokens/dark/amsterdam.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/amsterdam.css (sha256:de7e914b6d829780e6d5e565ee8c0af3154facaf773c835fdb638b72ba95c98d) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/amsterdam.css (sha256:de7e914b6d829780e6d5e565ee8c0af3154facaf773c835fdb638b72ba95c98d) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --amsterdam-color-blue: #5ea4f7; @@ -24,7 +24,7 @@ --nldesign-color-error-hover: #f54949; --nldesign-color-error-rgb: 243, 28, 28; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #5ea4f7; + --nldesign-color-focus: #5ea4f780; --nldesign-color-focus-rgb: 94, 164, 247; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #dfdfdf; @@ -57,7 +57,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #5ea4f7; + --nldesign-component-button-focus-border-color: #5ea4f780; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #79b3f8; --nldesign-component-button-hover-color: #66acff; @@ -79,6 +79,7 @@ --nldesign-component-heading-4-color: #dfdfdf; --nldesign-component-heading-5-color: #dfdfdf; --nldesign-component-heading-6-color: #dfdfdf; + --nldesign-component-heading-color: #dfdfdf; --nldesign-component-link-color: #66acff; --nldesign-component-link-hover-color: #88beff; --nldesign-component-link-visited-color: #ff5fd7; @@ -125,7 +126,7 @@ body[data-themes*=dark] { --nldesign-color-error-hover: #f54949; --nldesign-color-error-rgb: 243, 28, 28; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #5ea4f7; + --nldesign-color-focus: #5ea4f780; --nldesign-color-focus-rgb: 94, 164, 247; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #dfdfdf; @@ -158,7 +159,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #5ea4f7; + --nldesign-component-button-focus-border-color: #5ea4f780; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #79b3f8; --nldesign-component-button-hover-color: #66acff; @@ -180,6 +181,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #dfdfdf; --nldesign-component-heading-5-color: #dfdfdf; --nldesign-component-heading-6-color: #dfdfdf; + --nldesign-component-heading-color: #dfdfdf; --nldesign-component-link-color: #66acff; --nldesign-component-link-hover-color: #88beff; --nldesign-component-link-visited-color: #ff5fd7; diff --git a/css/tokens/dark/bodegraven-reeuwijk.css b/css/tokens/dark/bodegraven-reeuwijk.css index 194ecc27..da6a0410 100644 --- a/css/tokens/dark/bodegraven-reeuwijk.css +++ b/css/tokens/dark/bodegraven-reeuwijk.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/bodegraven-reeuwijk.css (sha256:9d604cd255d5b01230229757e537d0ed0d234b2de972716f5075becca5f8720a) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/bodegraven-reeuwijk.css (sha256:9d604cd255d5b01230229757e537d0ed0d234b2de972716f5075becca5f8720a) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --bodegraven-color-dataviz-1: #a9b63b; @@ -24,7 +24,7 @@ --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-grey-30: #c0c0c0; --nldesign-color-header-background: #141414; @@ -58,7 +58,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -80,6 +80,7 @@ --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -127,7 +128,7 @@ body[data-themes*=dark] { --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-grey-30: #c0c0c0; --nldesign-color-header-background: #141414; @@ -161,7 +162,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -183,6 +184,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; diff --git a/css/tokens/dark/borne.css b/css/tokens/dark/borne.css index b009f88e..a86fb8f7 100644 --- a/css/tokens/dark/borne.css +++ b/css/tokens/dark/borne.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/borne.css (sha256:df0a0b4462ba0f19d8a41b37d98e157e3572f82e85758e5fcf23e273876fb0aa) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/borne.css (sha256:df0a0b4462ba0f19d8a41b37d98e157e3572f82e85758e5fcf23e273876fb0aa) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --nldesign-color-background: #141414; @@ -15,7 +15,7 @@ --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -48,7 +48,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -70,6 +70,7 @@ --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -107,7 +108,7 @@ body[data-themes*=dark] { --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -140,7 +141,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -162,6 +163,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; diff --git a/css/tokens/dark/buren.css b/css/tokens/dark/buren.css index 5da2cd39..7f2dd34b 100644 --- a/css/tokens/dark/buren.css +++ b/css/tokens/dark/buren.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/buren.css (sha256:5e905928b4a4a885f97b147ffebaf8199affb79c68f2f5f3aff647f967480fe6) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/buren.css (sha256:5e905928b4a4a885f97b147ffebaf8199affb79c68f2f5f3aff647f967480fe6) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --buren-color-feedback-success-30: #73ea8f; @@ -29,7 +29,7 @@ --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; --nldesign-color-feedback-success-30: #73ea8f; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-geel-50: #efb810; --nldesign-color-grijs-100: #141414; @@ -74,7 +74,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -96,6 +96,7 @@ --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -147,7 +148,7 @@ body[data-themes*=dark] { --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; --nldesign-color-feedback-success-30: #73ea8f; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-geel-50: #efb810; --nldesign-color-grijs-100: #141414; @@ -192,7 +193,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -214,6 +215,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; diff --git a/css/tokens/dark/conduction-new.css b/css/tokens/dark/conduction-new.css index 513ac890..9a7cce4b 100644 --- a/css/tokens/dark/conduction-new.css +++ b/css/tokens/dark/conduction-new.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/conduction-new.css (sha256:ddb073217354a5e92d52d5df25d3c0d47d263a7bc56a9109343fe2697d6abc9d) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/conduction-new.css (sha256:ddb073217354a5e92d52d5df25d3c0d47d263a7bc56a9109343fe2697d6abc9d) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --c-blue-cobalt: #6f93d6; @@ -75,7 +75,7 @@ --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-footer-background: #b9cbed; --nldesign-color-footer-text: #9e9e9e; @@ -111,7 +111,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #131c2a; --nldesign-component-button-hover-border-color: #81a0dc; --nldesign-component-button-hover-color: #7499de; @@ -133,6 +133,7 @@ --nldesign-component-heading-4-color: #7499de; --nldesign-component-heading-5-color: #7499de; --nldesign-component-heading-6-color: #7499de; + --nldesign-component-heading-color: #7499de; --nldesign-component-link-color: #7499de; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -152,27 +153,27 @@ --nldesign-component-textbox-hover-border-color: #9e9e9e; --nldesign-component-textbox-invalid-border-color: #e75f55; --nldesign-component-unordered-list-color: #7499de; - --nldesign-footer-badge-border-color: #141414; - --nldesign-footer-badge-color: #9e9e9e; + --nldesign-footer-badge-border-color: #14141440; + --nldesign-footer-badge-color: #9e9e9ed9; --nldesign-footer-boat-color: #f58547; --nldesign-footer-boat-mark-color: #9e9e9e; - --nldesign-footer-brand-color: #9e9e9e; + --nldesign-footer-brand-color: #9e9e9eb3; --nldesign-footer-flag-bottom-color: #7499de; --nldesign-footer-flag-middle-color: #9e9e9e; --nldesign-footer-flag-top-color: #e45864; - --nldesign-footer-heading-color: #9e9e9e; + --nldesign-footer-heading-color: #9e9e9e8c; --nldesign-footer-legal-background: #b9cbed; - --nldesign-footer-legal-color: #9e9e9e; - --nldesign-footer-legal-link-color: #9e9e9e; - --nldesign-footer-legal-rule-color: #9e9e9e; - --nldesign-footer-link-color: #9e9e9e; + --nldesign-footer-legal-color: #9e9e9e8c; + --nldesign-footer-legal-link-color: #9e9e9ea6; + --nldesign-footer-legal-rule-color: #9e9e9e1a; + --nldesign-footer-link-color: #9e9e9ed9; --nldesign-footer-quay-accent-color: #c8543e; --nldesign-footer-quay-color: #9a9ca2; - --nldesign-footer-quay-highlight-color: #9e9e9e; + --nldesign-footer-quay-highlight-color: #9e9e9e80; --nldesign-footer-quay-ink-color: #d0ddf5; --nldesign-footer-skyline-color: #f58547; - --nldesign-footer-social-border-color: #141414; - --nldesign-footer-social-color: #9e9e9e; + --nldesign-footer-social-border-color: #1414142e; + --nldesign-footer-social-color: #9e9e9ec7; --nldesign-footer-water-background: #b9cbed; --nldesign-footer-window-dark-color: #d0ddf5; --nldesign-footer-window-lit-color: #ffdd3d; @@ -193,7 +194,7 @@ --nldesign-hero-body-color: #7792c5; --nldesign-hero-cell-accent-background: #d75b17; --nldesign-hero-cell-accent-color: #b9cbed; - --nldesign-hero-cell-background: #141414; + --nldesign-hero-cell-background: #14141414; --nldesign-hero-cell-color: #9e9e9e; --nldesign-hero-title-color: #9e9e9e; --nldesign-hero-title-icon-background: #d75b17; @@ -338,7 +339,7 @@ body[data-themes*=dark] { --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-footer-background: #b9cbed; --nldesign-color-footer-text: #9e9e9e; @@ -374,7 +375,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #131c2a; --nldesign-component-button-hover-border-color: #81a0dc; --nldesign-component-button-hover-color: #7499de; @@ -396,6 +397,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #7499de; --nldesign-component-heading-5-color: #7499de; --nldesign-component-heading-6-color: #7499de; + --nldesign-component-heading-color: #7499de; --nldesign-component-link-color: #7499de; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -415,27 +417,27 @@ body[data-themes*=dark] { --nldesign-component-textbox-hover-border-color: #9e9e9e; --nldesign-component-textbox-invalid-border-color: #e75f55; --nldesign-component-unordered-list-color: #7499de; - --nldesign-footer-badge-border-color: #141414; - --nldesign-footer-badge-color: #9e9e9e; + --nldesign-footer-badge-border-color: #14141440; + --nldesign-footer-badge-color: #9e9e9ed9; --nldesign-footer-boat-color: #f58547; --nldesign-footer-boat-mark-color: #9e9e9e; - --nldesign-footer-brand-color: #9e9e9e; + --nldesign-footer-brand-color: #9e9e9eb3; --nldesign-footer-flag-bottom-color: #7499de; --nldesign-footer-flag-middle-color: #9e9e9e; --nldesign-footer-flag-top-color: #e45864; - --nldesign-footer-heading-color: #9e9e9e; + --nldesign-footer-heading-color: #9e9e9e8c; --nldesign-footer-legal-background: #b9cbed; - --nldesign-footer-legal-color: #9e9e9e; - --nldesign-footer-legal-link-color: #9e9e9e; - --nldesign-footer-legal-rule-color: #9e9e9e; - --nldesign-footer-link-color: #9e9e9e; + --nldesign-footer-legal-color: #9e9e9e8c; + --nldesign-footer-legal-link-color: #9e9e9ea6; + --nldesign-footer-legal-rule-color: #9e9e9e1a; + --nldesign-footer-link-color: #9e9e9ed9; --nldesign-footer-quay-accent-color: #c8543e; --nldesign-footer-quay-color: #9a9ca2; - --nldesign-footer-quay-highlight-color: #9e9e9e; + --nldesign-footer-quay-highlight-color: #9e9e9e80; --nldesign-footer-quay-ink-color: #d0ddf5; --nldesign-footer-skyline-color: #f58547; - --nldesign-footer-social-border-color: #141414; - --nldesign-footer-social-color: #9e9e9e; + --nldesign-footer-social-border-color: #1414142e; + --nldesign-footer-social-color: #9e9e9ec7; --nldesign-footer-water-background: #b9cbed; --nldesign-footer-window-dark-color: #d0ddf5; --nldesign-footer-window-lit-color: #ffdd3d; @@ -456,7 +458,7 @@ body[data-themes*=dark] { --nldesign-hero-body-color: #7792c5; --nldesign-hero-cell-accent-background: #d75b17; --nldesign-hero-cell-accent-color: #b9cbed; - --nldesign-hero-cell-background: #141414; + --nldesign-hero-cell-background: #14141414; --nldesign-hero-cell-color: #9e9e9e; --nldesign-hero-title-color: #9e9e9e; --nldesign-hero-title-icon-background: #d75b17; diff --git a/css/tokens/dark/conduction.css b/css/tokens/dark/conduction.css index e5961c2f..dee75da5 100644 --- a/css/tokens/dark/conduction.css +++ b/css/tokens/dark/conduction.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/conduction.css (sha256:2f09bea6b58ef6a02e448b6e4a1d2bb599a97047198a14a712c1433fc05d1dfa) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/conduction.css (sha256:2f09bea6b58ef6a02e448b6e4a1d2bb599a97047198a14a712c1433fc05d1dfa) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --ams-date-input-background-color: #141414; @@ -84,7 +84,7 @@ --nldesign-color-error-hover: #cc2c24; --nldesign-color-error-rgb: 227, 74, 66; --nldesign-color-favorite: #d75b17; - --nldesign-color-focus: #8c6cc7; + --nldesign-color-focus: #8c6cc780; --nldesign-color-focus-rgb: 140, 108, 199; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -117,7 +117,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #8c6cc7; + --nldesign-component-button-focus-border-color: #8c6cc780; --nldesign-component-button-hover-background-color: #131c2a; --nldesign-component-button-hover-border-color: #5e77ad; --nldesign-component-button-hover-color: #7499de; @@ -139,6 +139,7 @@ --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #7499de; --nldesign-component-link-hover-color: #7499de; --nldesign-component-link-visited-color: #906fcf; @@ -166,6 +167,8 @@ --tilburg-color-blue-400: #5e77ad; --tilburg-color-blue-500: #6f93d6; --tilburg-color-code-background-color: #242424; + --tilburg-color-dark-alpha-100: #ebebeb1a; + --tilburg-color-dark-alpha-50: #ebebeb0d; --tilburg-color-extra-limegreen: #85ba40; --tilburg-color-extra-purple: #8c6cc7; --tilburg-color-extra-yellow: #d3b70f; @@ -184,6 +187,8 @@ --tilburg-color-green-200: #3d7e70; --tilburg-color-green-300: #69f7d9; --tilburg-color-green-400: #7cf7db; + --tilburg-color-light-alpha-100: #1414141a; + --tilburg-color-light-alpha-50: #1414140d; --tilburg-color-orange-100: #492204; --tilburg-color-orange-200: #92470c; --tilburg-color-orange-300: #d75b17; @@ -330,6 +335,7 @@ --utrecht-button-subtle-disabled-background-color: #17171b; --utrecht-button-subtle-disabled-color: #7f95bd; --utrecht-button-subtle-focus-color: #7499de; + --utrecht-button-subtle-hover-background-color: #ebebeb0d; --utrecht-button-subtle-hover-color: #7499de; --utrecht-checkbox-background-color: #141414; --utrecht-checkbox-border-color: #495c82; @@ -550,7 +556,7 @@ body[data-themes*=dark] { --nldesign-color-error-hover: #cc2c24; --nldesign-color-error-rgb: 227, 74, 66; --nldesign-color-favorite: #d75b17; - --nldesign-color-focus: #8c6cc7; + --nldesign-color-focus: #8c6cc780; --nldesign-color-focus-rgb: 140, 108, 199; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -583,7 +589,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #8c6cc7; + --nldesign-component-button-focus-border-color: #8c6cc780; --nldesign-component-button-hover-background-color: #131c2a; --nldesign-component-button-hover-border-color: #5e77ad; --nldesign-component-button-hover-color: #7499de; @@ -605,6 +611,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #7499de; --nldesign-component-link-hover-color: #7499de; --nldesign-component-link-visited-color: #906fcf; @@ -632,6 +639,8 @@ body[data-themes*=dark] { --tilburg-color-blue-400: #5e77ad; --tilburg-color-blue-500: #6f93d6; --tilburg-color-code-background-color: #242424; + --tilburg-color-dark-alpha-100: #ebebeb1a; + --tilburg-color-dark-alpha-50: #ebebeb0d; --tilburg-color-extra-limegreen: #85ba40; --tilburg-color-extra-purple: #8c6cc7; --tilburg-color-extra-yellow: #d3b70f; @@ -650,6 +659,8 @@ body[data-themes*=dark] { --tilburg-color-green-200: #3d7e70; --tilburg-color-green-300: #69f7d9; --tilburg-color-green-400: #7cf7db; + --tilburg-color-light-alpha-100: #1414141a; + --tilburg-color-light-alpha-50: #1414140d; --tilburg-color-orange-100: #492204; --tilburg-color-orange-200: #92470c; --tilburg-color-orange-300: #d75b17; @@ -796,6 +807,7 @@ body[data-themes*=dark] { --utrecht-button-subtle-disabled-background-color: #17171b; --utrecht-button-subtle-disabled-color: #7f95bd; --utrecht-button-subtle-focus-color: #7499de; + --utrecht-button-subtle-hover-background-color: #ebebeb0d; --utrecht-button-subtle-hover-color: #7499de; --utrecht-checkbox-background-color: #141414; --utrecht-checkbox-border-color: #495c82; diff --git a/css/tokens/dark/cunningham.css b/css/tokens/dark/cunningham.css index 76a20f97..680c8918 100644 --- a/css/tokens/dark/cunningham.css +++ b/css/tokens/dark/cunningham.css @@ -1,7 +1,7 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/cunningham.css (sha256:da626d8a9aafd79cac0a6e627027cf7bc250bbe4084bd472651efb8165865589) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/cunningham.css (sha256:b168d9b46cc0798427fe413e02ab5ee677447ebb48851e73995ff836c425f3d3) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { - --nldesign-color-background: #141414; + --nldesign-color-background: #292b2f; --nldesign-color-background-dark: #292b2f; --nldesign-color-background-darker: #3f4247; --nldesign-color-background-hover: #1e2022; @@ -15,7 +15,7 @@ --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #e2e3e4; @@ -48,7 +48,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #0c1c32; --nldesign-component-button-hover-border-color: #91aed6; --nldesign-component-button-hover-color: #6096e5; @@ -70,6 +70,7 @@ --nldesign-component-heading-4-color: #e2e3e4; --nldesign-component-heading-5-color: #e2e3e4; --nldesign-component-heading-6-color: #e2e3e4; + --nldesign-component-heading-color: #e2e3e4; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -77,8 +78,7 @@ --nldesign-component-paragraph-color: #e2e3e4; --nldesign-component-separator-border-color: #3f4247; --nldesign-component-table-border-color: #3f4247; - --nldesign-component-table-header-background-color: #292b2f; - --nldesign-component-table-header-color: #e2e3e4; + --nldesign-component-table-header-color: #9b9da1; --nldesign-component-table-row-hover-background-color: #1e2022; --nldesign-component-textbox-background-color: #9e9e9e; --nldesign-component-textbox-border-color: #989da4; @@ -93,7 +93,7 @@ } body[data-theme-dark], body[data-themes*=dark] { - --nldesign-color-background: #141414; + --nldesign-color-background: #292b2f; --nldesign-color-background-dark: #292b2f; --nldesign-color-background-darker: #3f4247; --nldesign-color-background-hover: #1e2022; @@ -107,7 +107,7 @@ body[data-themes*=dark] { --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #e2e3e4; @@ -140,7 +140,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #0c1c32; --nldesign-component-button-hover-border-color: #91aed6; --nldesign-component-button-hover-color: #6096e5; @@ -162,6 +162,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #e2e3e4; --nldesign-component-heading-5-color: #e2e3e4; --nldesign-component-heading-6-color: #e2e3e4; + --nldesign-component-heading-color: #e2e3e4; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -169,8 +170,7 @@ body[data-themes*=dark] { --nldesign-component-paragraph-color: #e2e3e4; --nldesign-component-separator-border-color: #3f4247; --nldesign-component-table-border-color: #3f4247; - --nldesign-component-table-header-background-color: #292b2f; - --nldesign-component-table-header-color: #e2e3e4; + --nldesign-component-table-header-color: #9b9da1; --nldesign-component-table-row-hover-background-color: #1e2022; --nldesign-component-textbox-background-color: #9e9e9e; --nldesign-component-textbox-border-color: #989da4; diff --git a/css/tokens/dark/demodam.css b/css/tokens/dark/demodam.css index 77eda037..fab49785 100644 --- a/css/tokens/dark/demodam.css +++ b/css/tokens/dark/demodam.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/demodam.css (sha256:ac5d956328d5e363cd8021e75166f4ea4fa6fc9c5205a273c02782de6918bd46) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/demodam.css (sha256:ac5d956328d5e363cd8021e75166f4ea4fa6fc9c5205a273c02782de6918bd46) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --demodam-color-blue-30: #16acf0; @@ -24,7 +24,7 @@ --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-green-10: #d7da37; --nldesign-color-grey-10: #7db3a5; @@ -62,7 +62,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -85,6 +85,7 @@ --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -131,7 +132,7 @@ body[data-themes*=dark] { --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-green-10: #d7da37; --nldesign-color-grey-10: #7db3a5; @@ -169,7 +170,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -192,6 +193,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; diff --git a/css/tokens/dark/denhaag.css b/css/tokens/dark/denhaag.css index e1658eed..c7b437df 100644 --- a/css/tokens/dark/denhaag.css +++ b/css/tokens/dark/denhaag.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/denhaag.css (sha256:7ae80c6f06039b1bbdccc66aa1e045d09674b8665426a9def0e8c5821b34613a) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/denhaag.css (sha256:7ae80c6f06039b1bbdccc66aa1e045d09674b8665426a9def0e8c5821b34613a) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --denhaag-color-blue: #59a5e4; @@ -21,7 +21,7 @@ --nldesign-color-error-hover: #d35555; --nldesign-color-error-rgb: 202, 50, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #59a5e4; + --nldesign-color-focus: #59a5e480; --nldesign-color-focus-rgb: 89, 165, 228; --nldesign-color-header-background: #7cdda0; --nldesign-color-header-text: #9e9e9e; @@ -54,7 +54,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #59a5e4; + --nldesign-component-button-focus-border-color: #59a5e480; --nldesign-component-button-hover-background-color: #173020; --nldesign-component-button-hover-border-color: #8ce2ac; --nldesign-component-button-hover-color: #85e5a9; @@ -76,6 +76,7 @@ --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #5cabed; --nldesign-component-link-hover-color: #85e5a9; --nldesign-component-link-visited-color: #9a74c8; @@ -119,7 +120,7 @@ body[data-themes*=dark] { --nldesign-color-error-hover: #d35555; --nldesign-color-error-rgb: 202, 50, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #59a5e4; + --nldesign-color-focus: #59a5e480; --nldesign-color-focus-rgb: 89, 165, 228; --nldesign-color-header-background: #7cdda0; --nldesign-color-header-text: #9e9e9e; @@ -152,7 +153,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #59a5e4; + --nldesign-component-button-focus-border-color: #59a5e480; --nldesign-component-button-hover-background-color: #173020; --nldesign-component-button-hover-border-color: #8ce2ac; --nldesign-component-button-hover-color: #85e5a9; @@ -174,6 +175,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #5cabed; --nldesign-component-link-hover-color: #85e5a9; --nldesign-component-link-visited-color: #9a74c8; diff --git a/css/tokens/dark/dinkelland.css b/css/tokens/dark/dinkelland.css index eb93ad08..25bf7d90 100644 --- a/css/tokens/dark/dinkelland.css +++ b/css/tokens/dark/dinkelland.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/dinkelland.css (sha256:3a078e61ad62346701ba107fd8d49cc5c97e9119cafb148104e62b75e3def45d) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/dinkelland.css (sha256:3a078e61ad62346701ba107fd8d49cc5c97e9119cafb148104e62b75e3def45d) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --dinkelland-color-alert-error-background: #420f13; @@ -60,7 +60,7 @@ --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 196, 44, 58; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-green-24: #77f8c8; --nldesign-color-grey-31: #a8a8a8; @@ -99,6 +99,8 @@ --nldesign-color-warning-rgb: 237, 181, 12; --nldesign-color-white-100: #141414; --nldesign-color-white-98: #191919; + --nldesign-component-badge-background-color: #81afe2; + --nldesign-component-badge-color: #9e9e9e; --nldesign-component-button-active-background-color: #172d41; --nldesign-component-button-active-color: #8cb9ea; --nldesign-component-button-border-color: #81afe2; @@ -106,7 +108,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -128,6 +130,7 @@ --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -210,7 +213,7 @@ body[data-themes*=dark] { --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 196, 44, 58; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-green-24: #77f8c8; --nldesign-color-grey-31: #a8a8a8; @@ -249,6 +252,8 @@ body[data-themes*=dark] { --nldesign-color-warning-rgb: 237, 181, 12; --nldesign-color-white-100: #141414; --nldesign-color-white-98: #191919; + --nldesign-component-badge-background-color: #81afe2; + --nldesign-component-badge-color: #9e9e9e; --nldesign-component-button-active-background-color: #172d41; --nldesign-component-button-active-color: #8cb9ea; --nldesign-component-button-border-color: #81afe2; @@ -256,7 +261,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -278,6 +283,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; diff --git a/css/tokens/dark/drechterland.css b/css/tokens/dark/drechterland.css index ff500d8e..1ffa23ce 100644 --- a/css/tokens/dark/drechterland.css +++ b/css/tokens/dark/drechterland.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/drechterland.css (sha256:cacfb205610bf1c47834423d9d801af7c5e8e1721470d70383a839ec571a8c2c) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/drechterland.css (sha256:cacfb205610bf1c47834423d9d801af7c5e8e1721470d70383a839ec571a8c2c) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --nldesign-color-background: #141414; @@ -15,7 +15,7 @@ --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -48,7 +48,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -70,6 +70,7 @@ --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -107,7 +108,7 @@ body[data-themes*=dark] { --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -140,7 +141,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -162,6 +163,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; diff --git a/css/tokens/dark/duiven.css b/css/tokens/dark/duiven.css index 366707ab..1355de7d 100644 --- a/css/tokens/dark/duiven.css +++ b/css/tokens/dark/duiven.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/duiven.css (sha256:3f49c03b7fefdd097e6566c482f5ae34377084667ee8383e210c25ecf7921f44) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/duiven.css (sha256:3f49c03b7fefdd097e6566c482f5ae34377084667ee8383e210c25ecf7921f44) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --nldesign-color-background: #141414; @@ -15,7 +15,7 @@ --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -48,7 +48,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -70,6 +70,7 @@ --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -107,7 +108,7 @@ body[data-themes*=dark] { --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -140,7 +141,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -162,6 +163,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; diff --git a/css/tokens/dark/duo.css b/css/tokens/dark/duo.css index 5f47a9ed..d4c7d086 100644 --- a/css/tokens/dark/duo.css +++ b/css/tokens/dark/duo.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/duo.css (sha256:3a276b6c0e3c239399edc3ea74bb17c11e322644493d8eef8edf8b535d32d1ba) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/duo.css (sha256:3a276b6c0e3c239399edc3ea74bb17c11e322644493d8eef8edf8b535d32d1ba) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --nldesign-color-background: #141414; @@ -15,7 +15,7 @@ --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -48,7 +48,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -70,6 +70,7 @@ --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -106,7 +107,7 @@ body[data-themes*=dark] { --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -139,7 +140,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -161,6 +162,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; diff --git a/css/tokens/dark/enkhuizen.css b/css/tokens/dark/enkhuizen.css index 14c65ab1..6974cb09 100644 --- a/css/tokens/dark/enkhuizen.css +++ b/css/tokens/dark/enkhuizen.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/enkhuizen.css (sha256:18f2e240361fd56a1c1ec5603429ce83bbf75d67527893dce19baf4cb9479ba3) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/enkhuizen.css (sha256:18f2e240361fd56a1c1ec5603429ce83bbf75d67527893dce19baf4cb9479ba3) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --nldesign-color-background: #141414; @@ -15,7 +15,7 @@ --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -48,7 +48,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -70,6 +70,7 @@ --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -107,7 +108,7 @@ body[data-themes*=dark] { --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -140,7 +141,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -162,6 +163,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; diff --git a/css/tokens/dark/epe.css b/css/tokens/dark/epe.css index b9bb7360..173cc961 100644 --- a/css/tokens/dark/epe.css +++ b/css/tokens/dark/epe.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/epe.css (sha256:9372c009ed74fbf0810a7965f4fbec4e07bb77d4b3ed727948f48345fa616cba) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/epe.css (sha256:9372c009ed74fbf0810a7965f4fbec4e07bb77d4b3ed727948f48345fa616cba) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --nldesign-brand-epe-color-alert-error-background: #420f13; @@ -43,7 +43,7 @@ --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -76,7 +76,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -98,6 +98,7 @@ --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -163,7 +164,7 @@ body[data-themes*=dark] { --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -196,7 +197,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -218,6 +219,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; diff --git a/css/tokens/dark/frankendesk.css b/css/tokens/dark/frankendesk.css index 17a22a4c..a62e2bae 100644 --- a/css/tokens/dark/frankendesk.css +++ b/css/tokens/dark/frankendesk.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/frankendesk.css (sha256:8c2897e412d71c280d8b6ad6c8bdb4601411ba6cea06561aa28e571810a80174) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/frankendesk.css (sha256:8c2897e412d71c280d8b6ad6c8bdb4601411ba6cea06561aa28e571810a80174) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --nldesign-card-background: #141414; @@ -19,7 +19,7 @@ --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-footer-background: #cbcbd6; --nldesign-color-footer-text: #9e9e9e; @@ -54,7 +54,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #0f162c; --nldesign-component-button-hover-border-color: #7f7dc3; --nldesign-component-button-hover-color: #7774c8; @@ -76,6 +76,7 @@ --nldesign-component-heading-4-color: #dcdce4; --nldesign-component-heading-5-color: #dcdce4; --nldesign-component-heading-6-color: #dcdce4; + --nldesign-component-heading-color: #dcdce4; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -95,14 +96,14 @@ --nldesign-component-textbox-hover-border-color: #9696a6; --nldesign-component-textbox-invalid-border-color: #e75f55; --nldesign-component-unordered-list-color: #dcdce4; - --nldesign-footer-brand-color: #9e9e9e; - --nldesign-footer-heading-color: #9e9e9e; - --nldesign-footer-legal-color: #9e9e9e; - --nldesign-footer-legal-link-color: #9e9e9e; - --nldesign-footer-legal-rule-color: #9e9e9e; + --nldesign-footer-brand-color: #9e9e9ebf; + --nldesign-footer-heading-color: #9e9e9e99; + --nldesign-footer-legal-color: #9e9e9e8c; + --nldesign-footer-legal-link-color: #9e9e9ebf; + --nldesign-footer-legal-rule-color: #9e9e9e1f; --nldesign-footer-link-color: #8f91ae; - --nldesign-footer-social-border-color: #141414; - --nldesign-footer-social-color: #9e9e9e; + --nldesign-footer-social-border-color: #14141433; + --nldesign-footer-social-color: #9e9e9ed9; --nldesign-footer-wordmark-color: #9e9e9e; --nldesign-header-background: #141414; --nldesign-header-border-color: #23232f; @@ -132,7 +133,7 @@ body[data-themes*=dark] { --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-footer-background: #cbcbd6; --nldesign-color-footer-text: #9e9e9e; @@ -167,7 +168,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #0f162c; --nldesign-component-button-hover-border-color: #7f7dc3; --nldesign-component-button-hover-color: #7774c8; @@ -189,6 +190,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #dcdce4; --nldesign-component-heading-5-color: #dcdce4; --nldesign-component-heading-6-color: #dcdce4; + --nldesign-component-heading-color: #dcdce4; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -208,14 +210,14 @@ body[data-themes*=dark] { --nldesign-component-textbox-hover-border-color: #9696a6; --nldesign-component-textbox-invalid-border-color: #e75f55; --nldesign-component-unordered-list-color: #dcdce4; - --nldesign-footer-brand-color: #9e9e9e; - --nldesign-footer-heading-color: #9e9e9e; - --nldesign-footer-legal-color: #9e9e9e; - --nldesign-footer-legal-link-color: #9e9e9e; - --nldesign-footer-legal-rule-color: #9e9e9e; + --nldesign-footer-brand-color: #9e9e9ebf; + --nldesign-footer-heading-color: #9e9e9e99; + --nldesign-footer-legal-color: #9e9e9e8c; + --nldesign-footer-legal-link-color: #9e9e9ebf; + --nldesign-footer-legal-rule-color: #9e9e9e1f; --nldesign-footer-link-color: #8f91ae; - --nldesign-footer-social-border-color: #141414; - --nldesign-footer-social-color: #9e9e9e; + --nldesign-footer-social-border-color: #14141433; + --nldesign-footer-social-color: #9e9e9ed9; --nldesign-footer-wordmark-color: #9e9e9e; --nldesign-header-background: #141414; --nldesign-header-border-color: #23232f; diff --git a/css/tokens/dark/groningen.css b/css/tokens/dark/groningen.css index c9a2e8b4..40edfeda 100644 --- a/css/tokens/dark/groningen.css +++ b/css/tokens/dark/groningen.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/groningen.css (sha256:4d6157821d7ad03124f99d9bcd56d5d924ace2ef94f50e9eab98ede618c7a4c4) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/groningen.css (sha256:4d6157821d7ad03124f99d9bcd56d5d924ace2ef94f50e9eab98ede618c7a4c4) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --nldesign-color-background: #141414; @@ -15,7 +15,7 @@ --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -48,7 +48,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -70,6 +70,7 @@ --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -107,7 +108,7 @@ body[data-themes*=dark] { --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -140,7 +141,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -162,6 +163,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; diff --git a/css/tokens/dark/haarlem.css b/css/tokens/dark/haarlem.css index 8ace8dc1..06ed6ff6 100644 --- a/css/tokens/dark/haarlem.css +++ b/css/tokens/dark/haarlem.css @@ -1,9 +1,10 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/haarlem.css (sha256:1c08fe55197743e083b293f67d4fe5f751c74830fbf9f56b40af5c64f76a944d) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/haarlem.css (sha256:1c08fe55197743e083b293f67d4fe5f751c74830fbf9f56b40af5c64f76a944d) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --haarlem-color-black: #bfbfc5; --haarlem-color-dark-mode-10: #1e1e1e; --haarlem-color-dark-mode-100: #bfbfc5; + --haarlem-color-dark-mode-25: #3f3f40c2; --haarlem-color-dark-mode-50: #6a6a6d; --haarlem-color-dark-mode-75: #959598; --haarlem-color-indigo-10: #141414; @@ -45,13 +46,14 @@ --nldesign-color-button-primary-text: #9e9e9e; --nldesign-color-dark-mode-10: #1e1e1e; --nldesign-color-dark-mode-100: #bfbfc5; + --nldesign-color-dark-mode-25: #3f3f40c2; --nldesign-color-dark-mode-50: #6a6a6d; --nldesign-color-dark-mode-75: #959598; --nldesign-color-error: #d83d32; --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -110,7 +112,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #646566; --nldesign-component-button-disabled-color: #9d9e9f; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -132,6 +134,7 @@ --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -158,6 +161,7 @@ body[data-themes*=dark] { --haarlem-color-black: #bfbfc5; --haarlem-color-dark-mode-10: #1e1e1e; --haarlem-color-dark-mode-100: #bfbfc5; + --haarlem-color-dark-mode-25: #3f3f40c2; --haarlem-color-dark-mode-50: #6a6a6d; --haarlem-color-dark-mode-75: #959598; --haarlem-color-indigo-10: #141414; @@ -199,13 +203,14 @@ body[data-themes*=dark] { --nldesign-color-button-primary-text: #9e9e9e; --nldesign-color-dark-mode-10: #1e1e1e; --nldesign-color-dark-mode-100: #bfbfc5; + --nldesign-color-dark-mode-25: #3f3f40c2; --nldesign-color-dark-mode-50: #6a6a6d; --nldesign-color-dark-mode-75: #959598; --nldesign-color-error: #d83d32; --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -264,7 +269,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #646566; --nldesign-component-button-disabled-color: #9d9e9f; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -286,6 +291,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; diff --git a/css/tokens/dark/haarlemmermeer.css b/css/tokens/dark/haarlemmermeer.css index 1b7d7e0d..54e5ce54 100644 --- a/css/tokens/dark/haarlemmermeer.css +++ b/css/tokens/dark/haarlemmermeer.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/haarlemmermeer.css (sha256:aed02c376434659300c331a849fbca586fbe16e0e7ec0d9fa8472804fb3bc567) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/haarlemmermeer.css (sha256:aed02c376434659300c331a849fbca586fbe16e0e7ec0d9fa8472804fb3bc567) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --haarlemmermeer-color-grey-45: #888888; @@ -16,7 +16,7 @@ --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-grey-45: #888888; --nldesign-color-header-background: #141414; @@ -50,7 +50,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -72,6 +72,7 @@ --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -110,7 +111,7 @@ body[data-themes*=dark] { --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-grey-45: #888888; --nldesign-color-header-background: #141414; @@ -144,7 +145,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -166,6 +167,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; diff --git a/css/tokens/dark/hoorn.css b/css/tokens/dark/hoorn.css index 01ffaa4f..c33ed9ee 100644 --- a/css/tokens/dark/hoorn.css +++ b/css/tokens/dark/hoorn.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/hoorn.css (sha256:ba90d4b84a3d03c23d9efac1bde32ad2ee061dcca83d80257ac4b6b7b244eab3) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/hoorn.css (sha256:ba90d4b84a3d03c23d9efac1bde32ad2ee061dcca83d80257ac4b6b7b244eab3) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --hoorn-color-blue-20: #85b5ee; @@ -17,7 +17,7 @@ --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -50,7 +50,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -72,6 +72,7 @@ --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -111,7 +112,7 @@ body[data-themes*=dark] { --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -144,7 +145,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -166,6 +167,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; diff --git a/css/tokens/dark/horstaandemaas.css b/css/tokens/dark/horstaandemaas.css index 62c46399..f7074417 100644 --- a/css/tokens/dark/horstaandemaas.css +++ b/css/tokens/dark/horstaandemaas.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/horstaandemaas.css (sha256:f21c9618f94c81fee62ca2f97295ddb7b9da9372701bd84791d8f7c6fc6a3d37) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/horstaandemaas.css (sha256:f21c9618f94c81fee62ca2f97295ddb7b9da9372701bd84791d8f7c6fc6a3d37) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --nldesign-color-background: #141414; @@ -15,7 +15,7 @@ --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -48,7 +48,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -70,6 +70,7 @@ --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -107,7 +108,7 @@ body[data-themes*=dark] { --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -140,7 +141,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -162,6 +163,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; diff --git a/css/tokens/dark/lasuite.css b/css/tokens/dark/lasuite.css index d2da7a4c..4ae64214 100644 --- a/css/tokens/dark/lasuite.css +++ b/css/tokens/dark/lasuite.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/lasuite.css (sha256:d89f33ce0ac340c70fbf9f90a5d21d720bcc282e199558b948fc661ff368a960) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/lasuite.css (sha256:d89f33ce0ac340c70fbf9f90a5d21d720bcc282e199558b948fc661ff368a960) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --nldesign-color-background: #141414; @@ -15,7 +15,7 @@ --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #dcdce4; @@ -48,7 +48,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #0f162c; --nldesign-component-button-hover-border-color: #7f7dc3; --nldesign-component-button-hover-color: #7774c8; @@ -70,6 +70,7 @@ --nldesign-component-heading-4-color: #dcdce4; --nldesign-component-heading-5-color: #dcdce4; --nldesign-component-heading-6-color: #dcdce4; + --nldesign-component-heading-color: #dcdce4; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -107,7 +108,7 @@ body[data-themes*=dark] { --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #dcdce4; @@ -140,7 +141,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #0f162c; --nldesign-component-button-hover-border-color: #7f7dc3; --nldesign-component-button-hover-color: #7774c8; @@ -162,6 +163,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #dcdce4; --nldesign-component-heading-5-color: #dcdce4; --nldesign-component-heading-6-color: #dcdce4; + --nldesign-component-heading-color: #dcdce4; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; diff --git a/css/tokens/dark/leiden.css b/css/tokens/dark/leiden.css index 833f0a10..10ece744 100644 --- a/css/tokens/dark/leiden.css +++ b/css/tokens/dark/leiden.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/leiden.css (sha256:4f3648bb38f2021f65db7fbe7968cd7d01192991726f42cb6d20680b8430f07b) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/leiden.css (sha256:4f3648bb38f2021f65db7fbe7968cd7d01192991726f42cb6d20680b8430f07b) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --leiden-color-alert-error: #db838b; @@ -56,7 +56,7 @@ --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 229, 66, 47; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-grey-11: #d4d4d2; --nldesign-color-grey-31: #a8a8a8; @@ -95,6 +95,8 @@ --nldesign-color-warning: #edb50c; --nldesign-color-warning-rgb: 237, 181, 12; --nldesign-color-white-100: #141414; + --nldesign-component-badge-background-color: #e5422f; + --nldesign-component-badge-color: #9e9e9e; --nldesign-component-button-active-background-color: #172d41; --nldesign-component-button-active-color: #f25c4b; --nldesign-component-button-border-color: #e5422f; @@ -102,7 +104,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #e75747; --nldesign-component-button-hover-color: #f25c4b; @@ -124,6 +126,7 @@ --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -202,7 +205,7 @@ body[data-themes*=dark] { --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 229, 66, 47; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-grey-11: #d4d4d2; --nldesign-color-grey-31: #a8a8a8; @@ -241,6 +244,8 @@ body[data-themes*=dark] { --nldesign-color-warning: #edb50c; --nldesign-color-warning-rgb: 237, 181, 12; --nldesign-color-white-100: #141414; + --nldesign-component-badge-background-color: #e5422f; + --nldesign-component-badge-color: #9e9e9e; --nldesign-component-button-active-background-color: #172d41; --nldesign-component-button-active-color: #f25c4b; --nldesign-component-button-border-color: #e5422f; @@ -248,7 +253,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #e75747; --nldesign-component-button-hover-color: #f25c4b; @@ -270,6 +275,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; diff --git a/css/tokens/dark/leidschendam-voorburg.css b/css/tokens/dark/leidschendam-voorburg.css index 6cb30f74..59ec88bf 100644 --- a/css/tokens/dark/leidschendam-voorburg.css +++ b/css/tokens/dark/leidschendam-voorburg.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/leidschendam-voorburg.css (sha256:49ec3b0c58300cede5911b557d1d410026a23e534775451c7869f3a37d962703) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/leidschendam-voorburg.css (sha256:49ec3b0c58300cede5911b557d1d410026a23e534775451c7869f3a37d962703) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --nldesign-color-background: #141414; @@ -15,7 +15,7 @@ --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -48,7 +48,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -70,6 +70,7 @@ --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -107,7 +108,7 @@ body[data-themes*=dark] { --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -140,7 +141,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -162,6 +163,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; diff --git a/css/tokens/dark/nijmegen.css b/css/tokens/dark/nijmegen.css index 9162362c..d8946597 100644 --- a/css/tokens/dark/nijmegen.css +++ b/css/tokens/dark/nijmegen.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/nijmegen.css (sha256:cb7bd11292cec9a40695d80345abf2fd2d6e3ccd705e790206ac1bd8d1b8c1fa) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/nijmegen.css (sha256:cb7bd11292cec9a40695d80345abf2fd2d6e3ccd705e790206ac1bd8d1b8c1fa) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --nijmegen-color-black: #ebebeb; @@ -13,6 +13,8 @@ --nijmegen-color-blue-800: #74c5f8; --nijmegen-color-blue-900: #88cef9; --nijmegen-color-blue-950: #acddfb; + --nijmegen-color-dark-alpha-100: #ebebeb1a; + --nijmegen-color-dark-alpha-50: #ebebeb0d; --nijmegen-color-feedback-blue-60: #0a87bf; --nijmegen-color-feedback-green-50: #b3c12c; --nijmegen-color-feedback-orange-50: #f2a70d; @@ -56,6 +58,8 @@ --nijmegen-color-grey-85: #313940; --nijmegen-color-grey-85b: #333333; --nijmegen-color-grey-95: #222222; + --nijmegen-color-light-alpha-100: #1414141a; + --nijmegen-color-light-alpha-50: #1414140d; --nijmegen-color-orange-100: #452104; --nijmegen-color-orange-200: #723506; --nijmegen-color-orange-300: #a24b09; @@ -147,6 +151,8 @@ --nldesign-color-button-primary-border: #81afe2; --nldesign-color-button-primary-hover: #6397da; --nldesign-color-button-primary-text: #9e9e9e; + --nldesign-color-dark-alpha-100: #ebebeb1a; + --nldesign-color-dark-alpha-50: #ebebeb0d; --nldesign-color-error: #d83d32; --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; @@ -155,7 +161,7 @@ --nldesign-color-feedback-green-50: #b3c12c; --nldesign-color-feedback-orange-50: #f2a70d; --nldesign-color-feedback-red-40: #f42e2e; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-gray-100: #1d2328; --nldesign-color-gray-200: #2e363f; @@ -200,6 +206,8 @@ --nldesign-color-header-text: #cccccc; --nldesign-color-info: #39adf5; --nldesign-color-info-rgb: 57, 173, 245; + --nldesign-color-light-alpha-100: #1414141a; + --nldesign-color-light-alpha-50: #1414140d; --nldesign-color-link: #81afe2; --nldesign-color-link-hover: #39adf5; --nldesign-color-link-visited: #c392e3; @@ -360,12 +368,14 @@ --nldesign-component-button-secondary-action-hover-background-color: #171a20; --nldesign-component-button-secondary-action-hover-border-color: #5e2ce2; --nldesign-component-button-secondary-action-hover-color: #794df0; + --nldesign-component-button-subtle-active-background-color: #ebebeb1a; --nldesign-component-button-subtle-active-color: #7c50f0; --nldesign-component-button-subtle-color: #723dff; --nldesign-component-button-subtle-disabled-background-color: #171a20; --nldesign-component-button-subtle-disabled-color: #8e9cae; --nldesign-component-button-subtle-focus-background-color: #19044f; --nldesign-component-button-subtle-focus-color: #ebebeb; + --nldesign-component-button-subtle-hover-background-color: #ebebeb0d; --nldesign-component-button-subtle-hover-color: #794df0; --nldesign-component-checkbox-active-background-color: #1d2328; --nldesign-component-checkbox-active-border-color: #637186; @@ -621,6 +631,8 @@ body[data-themes*=dark] { --nijmegen-color-blue-800: #74c5f8; --nijmegen-color-blue-900: #88cef9; --nijmegen-color-blue-950: #acddfb; + --nijmegen-color-dark-alpha-100: #ebebeb1a; + --nijmegen-color-dark-alpha-50: #ebebeb0d; --nijmegen-color-feedback-blue-60: #0a87bf; --nijmegen-color-feedback-green-50: #b3c12c; --nijmegen-color-feedback-orange-50: #f2a70d; @@ -664,6 +676,8 @@ body[data-themes*=dark] { --nijmegen-color-grey-85: #313940; --nijmegen-color-grey-85b: #333333; --nijmegen-color-grey-95: #222222; + --nijmegen-color-light-alpha-100: #1414141a; + --nijmegen-color-light-alpha-50: #1414140d; --nijmegen-color-orange-100: #452104; --nijmegen-color-orange-200: #723506; --nijmegen-color-orange-300: #a24b09; @@ -755,6 +769,8 @@ body[data-themes*=dark] { --nldesign-color-button-primary-border: #81afe2; --nldesign-color-button-primary-hover: #6397da; --nldesign-color-button-primary-text: #9e9e9e; + --nldesign-color-dark-alpha-100: #ebebeb1a; + --nldesign-color-dark-alpha-50: #ebebeb0d; --nldesign-color-error: #d83d32; --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; @@ -763,7 +779,7 @@ body[data-themes*=dark] { --nldesign-color-feedback-green-50: #b3c12c; --nldesign-color-feedback-orange-50: #f2a70d; --nldesign-color-feedback-red-40: #f42e2e; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-gray-100: #1d2328; --nldesign-color-gray-200: #2e363f; @@ -808,6 +824,8 @@ body[data-themes*=dark] { --nldesign-color-header-text: #cccccc; --nldesign-color-info: #39adf5; --nldesign-color-info-rgb: 57, 173, 245; + --nldesign-color-light-alpha-100: #1414141a; + --nldesign-color-light-alpha-50: #1414140d; --nldesign-color-link: #81afe2; --nldesign-color-link-hover: #39adf5; --nldesign-color-link-visited: #c392e3; @@ -968,12 +986,14 @@ body[data-themes*=dark] { --nldesign-component-button-secondary-action-hover-background-color: #171a20; --nldesign-component-button-secondary-action-hover-border-color: #5e2ce2; --nldesign-component-button-secondary-action-hover-color: #794df0; + --nldesign-component-button-subtle-active-background-color: #ebebeb1a; --nldesign-component-button-subtle-active-color: #7c50f0; --nldesign-component-button-subtle-color: #723dff; --nldesign-component-button-subtle-disabled-background-color: #171a20; --nldesign-component-button-subtle-disabled-color: #8e9cae; --nldesign-component-button-subtle-focus-background-color: #19044f; --nldesign-component-button-subtle-focus-color: #ebebeb; + --nldesign-component-button-subtle-hover-background-color: #ebebeb0d; --nldesign-component-button-subtle-hover-color: #794df0; --nldesign-component-checkbox-active-background-color: #1d2328; --nldesign-component-checkbox-active-border-color: #637186; diff --git a/css/tokens/dark/noaberkracht.css b/css/tokens/dark/noaberkracht.css index 44ddbbed..b4728b30 100644 --- a/css/tokens/dark/noaberkracht.css +++ b/css/tokens/dark/noaberkracht.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/noaberkracht.css (sha256:7086d19cd654c71cf78eec1e18befb77efbb3880b342bde8d6b74f6065b25c06) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/noaberkracht.css (sha256:7086d19cd654c71cf78eec1e18befb77efbb3880b342bde8d6b74f6065b25c06) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --nldesign-color-alert-error: #db838b; @@ -16,6 +16,10 @@ --nldesign-color-black-0: #ebebeb; --nldesign-color-blue-22: #659df7; --nldesign-color-blue-31: #5b86f5; + --nldesign-color-blue-31-10t: #5b86f51a; + --nldesign-color-blue-31-50t: #5b86f580; + --nldesign-color-blue-31-60t: #5b86f599; + --nldesign-color-blue-31-80t: #5b86f5cc; --nldesign-color-blue-46: #347bdc; --nldesign-color-blue-59: #425998; --nldesign-color-blue-93: #151c32; @@ -29,7 +33,7 @@ --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 196, 44, 58; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-grey-31: #a8a8a8; --nldesign-color-grey-46: #878787; @@ -62,6 +66,8 @@ --nldesign-color-white-100: #141414; --nldesign-color-white-98: #191919; --nldesign-color-yellow-47: #f3c019; + --nldesign-component-badge-background-color: #0d3dbc; + --nldesign-component-badge-color: #9e9e9e; --nldesign-component-button-active-background-color: #172d41; --nldesign-component-button-active-color: #4074fc; --nldesign-component-button-border-color: #0d3dbc; @@ -69,7 +75,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #2f5ac9; --nldesign-component-button-hover-color: #4074fc; @@ -91,6 +97,7 @@ --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -121,6 +128,10 @@ --noaberkracht-color-black-0: #ebebeb; --noaberkracht-color-blue-22: #659df7; --noaberkracht-color-blue-31: #5b86f5; + --noaberkracht-color-blue-31-10t: #5b86f51a; + --noaberkracht-color-blue-31-50t: #5b86f580; + --noaberkracht-color-blue-31-60t: #5b86f599; + --noaberkracht-color-blue-31-80t: #5b86f5cc; --noaberkracht-color-blue-46: #347bdc; --noaberkracht-color-blue-59: #425998; --noaberkracht-color-blue-93: #151c32; @@ -156,6 +167,10 @@ body[data-themes*=dark] { --nldesign-color-black-0: #ebebeb; --nldesign-color-blue-22: #659df7; --nldesign-color-blue-31: #5b86f5; + --nldesign-color-blue-31-10t: #5b86f51a; + --nldesign-color-blue-31-50t: #5b86f580; + --nldesign-color-blue-31-60t: #5b86f599; + --nldesign-color-blue-31-80t: #5b86f5cc; --nldesign-color-blue-46: #347bdc; --nldesign-color-blue-59: #425998; --nldesign-color-blue-93: #151c32; @@ -169,7 +184,7 @@ body[data-themes*=dark] { --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 196, 44, 58; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-grey-31: #a8a8a8; --nldesign-color-grey-46: #878787; @@ -202,6 +217,8 @@ body[data-themes*=dark] { --nldesign-color-white-100: #141414; --nldesign-color-white-98: #191919; --nldesign-color-yellow-47: #f3c019; + --nldesign-component-badge-background-color: #0d3dbc; + --nldesign-component-badge-color: #9e9e9e; --nldesign-component-button-active-background-color: #172d41; --nldesign-component-button-active-color: #4074fc; --nldesign-component-button-border-color: #0d3dbc; @@ -209,7 +226,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #2f5ac9; --nldesign-component-button-hover-color: #4074fc; @@ -231,6 +248,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -261,6 +279,10 @@ body[data-themes*=dark] { --noaberkracht-color-black-0: #ebebeb; --noaberkracht-color-blue-22: #659df7; --noaberkracht-color-blue-31: #5b86f5; + --noaberkracht-color-blue-31-10t: #5b86f51a; + --noaberkracht-color-blue-31-50t: #5b86f580; + --noaberkracht-color-blue-31-60t: #5b86f599; + --noaberkracht-color-blue-31-80t: #5b86f5cc; --noaberkracht-color-blue-46: #347bdc; --noaberkracht-color-blue-59: #425998; --noaberkracht-color-blue-93: #151c32; diff --git a/css/tokens/dark/noordoostpolder.css b/css/tokens/dark/noordoostpolder.css index c589890a..cfc667ec 100644 --- a/css/tokens/dark/noordoostpolder.css +++ b/css/tokens/dark/noordoostpolder.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/noordoostpolder.css (sha256:eef546cf8b23ac66e5992d524cc40e9ec98b7161648adce6f9190aa66930b06f) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/noordoostpolder.css (sha256:eef546cf8b23ac66e5992d524cc40e9ec98b7161648adce6f9190aa66930b06f) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --nldesign-color-background: #141414; @@ -15,7 +15,7 @@ --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -50,7 +50,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -72,6 +72,7 @@ --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -111,7 +112,7 @@ body[data-themes*=dark] { --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -146,7 +147,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -168,6 +169,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; diff --git a/css/tokens/dark/noordwijk.css b/css/tokens/dark/noordwijk.css index 1010f0cd..6ca30147 100644 --- a/css/tokens/dark/noordwijk.css +++ b/css/tokens/dark/noordwijk.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/noordwijk.css (sha256:4981ad3abcb266151212970a734cb616e636c151ec15637c35846ca30a7f7632) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/noordwijk.css (sha256:4981ad3abcb266151212970a734cb616e636c151ec15637c35846ca30a7f7632) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --nldesign-color-alert-error: #db838b; @@ -27,7 +27,7 @@ --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 196, 44, 58; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-grey-29: #acacac; --nldesign-color-grey-31: #a8a8a8; @@ -69,6 +69,8 @@ --nldesign-color-white-100: #141414; --nldesign-color-white-95: #1f1f1f; --nldesign-color-white-98: #191919; + --nldesign-component-badge-background-color: #81afe2; + --nldesign-component-badge-color: #9e9e9e; --nldesign-component-button-active-background-color: #172d41; --nldesign-component-button-active-color: #8cb9ea; --nldesign-component-button-border-color: #81afe2; @@ -76,7 +78,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -92,11 +94,13 @@ --nldesign-component-form-fieldset-border-color: #535353; --nldesign-component-form-select-border-color: #535353; --nldesign-component-form-select-focus-border-color: #39adf5; + --nldesign-component-heading-1-color: #cccccc; --nldesign-component-heading-2-color: #cccccc; --nldesign-component-heading-3-color: #cccccc; --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -178,7 +182,7 @@ body[data-themes*=dark] { --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 196, 44, 58; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-grey-29: #acacac; --nldesign-color-grey-31: #a8a8a8; @@ -220,6 +224,8 @@ body[data-themes*=dark] { --nldesign-color-white-100: #141414; --nldesign-color-white-95: #1f1f1f; --nldesign-color-white-98: #191919; + --nldesign-component-badge-background-color: #81afe2; + --nldesign-component-badge-color: #9e9e9e; --nldesign-component-button-active-background-color: #172d41; --nldesign-component-button-active-color: #8cb9ea; --nldesign-component-button-border-color: #81afe2; @@ -227,7 +233,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -243,11 +249,13 @@ body[data-themes*=dark] { --nldesign-component-form-fieldset-border-color: #535353; --nldesign-component-form-select-border-color: #535353; --nldesign-component-form-select-focus-border-color: #39adf5; + --nldesign-component-heading-1-color: #cccccc; --nldesign-component-heading-2-color: #cccccc; --nldesign-component-heading-3-color: #cccccc; --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; diff --git a/css/tokens/dark/opencatalogi.css b/css/tokens/dark/opencatalogi.css index 7a3a8c90..91d8d4c8 100644 --- a/css/tokens/dark/opencatalogi.css +++ b/css/tokens/dark/opencatalogi.css @@ -1,24 +1,39 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/opencatalogi.css (sha256:ed99e20b49b75e8834ddb26daea388c448d11fbb44d01ee302c1e6b4c8d4a31c) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/opencatalogi.css (sha256:ed99e20b49b75e8834ddb26daea388c448d11fbb44d01ee302c1e6b4c8d4a31c) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --ams-date-input-background-color: #141414; --ams-date-input-border-color: #717f83; --ams-date-input-color: #b0bbbf; + --ams-pagination-button-active-background-color: #ebebeb00; + --ams-pagination-button-active-border-color: #ebebeb00; --ams-pagination-button-active-color: #77bbff; + --ams-pagination-button-background-color: #ebebeb00; + --ams-pagination-button-border-color: #ebebeb00; --ams-pagination-button-color: #77bbff; --ams-pagination-button-current-background-color: #191e22; + --ams-pagination-button-current-border-color: #ebebeb00; --ams-pagination-button-current-color: #cccccc; --ams-pagination-button-focus-background-color: #45b9f5; --ams-pagination-button-focus-color: #77bbff; + --ams-pagination-button-hover-background-color: #ebebeb00; + --ams-pagination-button-hover-border-color: #ebebeb00; --ams-pagination-button-hover-color: #77bbff; --ams-pagination-color: #77bbff; --ams-pagination-description-color: #cccccc; --ams-pagination-ellipses-color: #8a9ab2; + --ams-pagination-relative-link-active-background-color: #ebebeb00; + --ams-pagination-relative-link-active-border-color: #ebebeb00; --ams-pagination-relative-link-active-color: #77bbff; + --ams-pagination-relative-link-background-color: #ebebeb00; + --ams-pagination-relative-link-border-color: #ebebeb00; --ams-pagination-relative-link-color: #77bbff; + --ams-pagination-relative-link-disabled-background-color: #ebebeb00; + --ams-pagination-relative-link-disabled-border-color: #ebebeb00; --ams-pagination-relative-link-disabled-color: #8e9cae; --ams-pagination-relative-link-focus-background-color: #45b9f5; --ams-pagination-relative-link-focus-color: #77bbff; + --ams-pagination-relative-link-hover-background-color: #ebebeb00; + --ams-pagination-relative-link-hover-border-color: #ebebeb00; --ams-pagination-relative-link-hover-color: #77bbff; --conduction-card-header-color: #b0bbbf; --conduction-card-header-date-color: #b0bbbf; @@ -59,8 +74,11 @@ --conduction-pagination-item-background-color: #141414; --conduction-pagination-item-border-color: #141414; --conduction-pagination-item-color: #b0bbbf; + --conduction-pagination-navigation-button-background-color: #ebebeb00; --conduction-pagination-navigation-button-color: #b0bbbf; + --conduction-pagination-navigation-button-disabled-background-color: #ebebeb00; --conduction-pagination-navigation-button-disabled-color: #93a2a9; + --conduction-pagination-navigation-button-hover-background-color: #ebebeb00; --conduction-pagination-navigation-button-hover-color: #7eff9d; --conduction-pagination-page-hover-background-color: #141414; --conduction-pagination-page-hover-border-color: #71f892; @@ -77,6 +95,7 @@ --conduction-primary-top-nav-dropdown-hover-color: #9e9e9e; --conduction-primary-top-nav-hover-background-color: #141414; --conduction-primary-top-nav-hover-color: #7eff9d; + --conduction-secondary-top-nav-background-color: #ebebeb00; --conduction-secondary-top-nav-color: #b0bbbf; --conduction-secondary-top-nav-current-color: #b0bbbf; --conduction-secondary-top-nav-hover-background-color: #71f892; @@ -89,9 +108,12 @@ --conduction-tabs-scroll-button-color: #b0bbbf; --conduction-tabs-scroll-button-hover-background-color: #141414; --conduction-tabs-scroll-button-hover-color: #7eff9d; + --conduction-tabs-tab-background-color: #ebebeb00; --conduction-tabs-tab-color: #b0bbbf; + --conduction-tabs-tab-hover-background-color: #ebebeb00; --conduction-tabs-tab-hover-color: #7eff9d; --conduction-tabs-tab-list-border-bottom-color: #172227; + --conduction-tabs-tab-selected-background-color: #ebebeb00; --conduction-tabs-tab-selected-border-bottom-color: #71f892; --conduction-tabs-tab-selected-color: #b0bbbf; --conduction-tabs-tab-selected-hover-color: #9ad2ff; @@ -206,6 +228,7 @@ --nldesign-component-heading-4-color: #b0bbbf; --nldesign-component-heading-5-color: #b0bbbf; --nldesign-component-heading-6-color: #b0bbbf; + --nldesign-component-heading-color: #b0bbbf; --nldesign-component-link-color: #7eff9d; --nldesign-component-link-hover-color: #91ffc3; --nldesign-component-link-visited-color: #e88fbf; @@ -306,6 +329,8 @@ --tilburg-color-blue-400: #61c4f5; --tilburg-color-blue-500: #88c6f9; --tilburg-color-code-background-color: #172227; + --tilburg-color-dark-alpha-100: #ebebeb1a; + --tilburg-color-dark-alpha-50: #ebebeb0d; --tilburg-color-extra-limegreen: #85ba40; --tilburg-color-extra-purple: #8c6cc7; --tilburg-color-extra-yellow: #d3b70f; @@ -324,6 +349,8 @@ --tilburg-color-green-200: #3d7e70; --tilburg-color-green-300: #69f7d9; --tilburg-color-green-400: #7cf7db; + --tilburg-color-light-alpha-100: #1414141a; + --tilburg-color-light-alpha-50: #1414140d; --tilburg-color-orange-100: #492204; --tilburg-color-orange-200: #92470c; --tilburg-color-orange-300: #ed7719; @@ -503,6 +530,7 @@ --utrecht-button-secondary-action-focus-color: #77bbff; --utrecht-button-secondary-action-hover-background-color: #a7b2b6; --utrecht-button-secondary-action-hover-color: #77bbff; + --utrecht-button-subtle-active-background-color: #ebebeb1a; --utrecht-button-subtle-active-color: #77bbff; --utrecht-button-subtle-background-color: #141414; --utrecht-button-subtle-color: #7eff9d; @@ -510,15 +538,21 @@ --utrecht-button-subtle-disabled-color: #95a2a8; --utrecht-button-subtle-focus-background-color: #45b9f5; --utrecht-button-subtle-focus-color: #77bbff; + --utrecht-button-subtle-hover-background-color: #ebebeb0d; --utrecht-button-subtle-hover-color: #9fffcd; --utrecht-calendar-navigation-color: #7eff9d; --utrecht-calendar-navigation-label-color: #ebebeb; + --utrecht-calendar-table-days-item-day-active-border-color: #ebebeb00; + --utrecht-calendar-table-days-item-day-border-color: #ebebeb00; --utrecht-calendar-table-days-item-day-color: #ebebeb; + --utrecht-calendar-table-days-item-day-emphasis-border-color: #ebebeb00; + --utrecht-calendar-table-days-item-day-focus-border-color: #ebebeb00; --utrecht-calendar-table-days-item-day-hover-border-color: #ebebeb; --utrecht-calendar-table-days-item-day-hover-color: #7eff9d; --utrecht-calendar-table-days-item-day-is-today-background-color: #033b0e; --utrecht-calendar-table-days-item-day-is-today-border-color: #71f892; --utrecht-calendar-table-days-item-day-is-today-color: #7eff9d; + --utrecht-calendar-table-days-item-day-out-of-the-month-border-color: #ebebeb00; --utrecht-calendar-table-days-item-day-out-of-the-month-color: #8da7af; --utrecht-calendar-table-days-item-day-selected-background-color: #c23d76; --utrecht-calendar-table-days-item-day-selected-color: #9e9e9e; @@ -669,8 +703,10 @@ --utrecht-select-invalid-color: #c8dfff; --utrecht-separator-color: #172227; --utrecht-skip-link-background-color: #71f892; + --utrecht-skip-link-border-color: #ebebeb00; --utrecht-skip-link-color: #9e9e9e; --utrecht-skip-link-focus-background-color: #45b9f5; + --utrecht-skip-link-focus-border-color: #ebebeb00; --utrecht-skip-link-focus-color: #77bbff; --utrecht-spotlight-section-background-color: #0c2534; --utrecht-spotlight-section-border-color: #16445d; @@ -761,21 +797,36 @@ body[data-themes*=dark] { --ams-date-input-background-color: #141414; --ams-date-input-border-color: #717f83; --ams-date-input-color: #b0bbbf; + --ams-pagination-button-active-background-color: #ebebeb00; + --ams-pagination-button-active-border-color: #ebebeb00; --ams-pagination-button-active-color: #77bbff; + --ams-pagination-button-background-color: #ebebeb00; + --ams-pagination-button-border-color: #ebebeb00; --ams-pagination-button-color: #77bbff; --ams-pagination-button-current-background-color: #191e22; + --ams-pagination-button-current-border-color: #ebebeb00; --ams-pagination-button-current-color: #cccccc; --ams-pagination-button-focus-background-color: #45b9f5; --ams-pagination-button-focus-color: #77bbff; + --ams-pagination-button-hover-background-color: #ebebeb00; + --ams-pagination-button-hover-border-color: #ebebeb00; --ams-pagination-button-hover-color: #77bbff; --ams-pagination-color: #77bbff; --ams-pagination-description-color: #cccccc; --ams-pagination-ellipses-color: #8a9ab2; + --ams-pagination-relative-link-active-background-color: #ebebeb00; + --ams-pagination-relative-link-active-border-color: #ebebeb00; --ams-pagination-relative-link-active-color: #77bbff; + --ams-pagination-relative-link-background-color: #ebebeb00; + --ams-pagination-relative-link-border-color: #ebebeb00; --ams-pagination-relative-link-color: #77bbff; + --ams-pagination-relative-link-disabled-background-color: #ebebeb00; + --ams-pagination-relative-link-disabled-border-color: #ebebeb00; --ams-pagination-relative-link-disabled-color: #8e9cae; --ams-pagination-relative-link-focus-background-color: #45b9f5; --ams-pagination-relative-link-focus-color: #77bbff; + --ams-pagination-relative-link-hover-background-color: #ebebeb00; + --ams-pagination-relative-link-hover-border-color: #ebebeb00; --ams-pagination-relative-link-hover-color: #77bbff; --conduction-card-header-color: #b0bbbf; --conduction-card-header-date-color: #b0bbbf; @@ -816,8 +867,11 @@ body[data-themes*=dark] { --conduction-pagination-item-background-color: #141414; --conduction-pagination-item-border-color: #141414; --conduction-pagination-item-color: #b0bbbf; + --conduction-pagination-navigation-button-background-color: #ebebeb00; --conduction-pagination-navigation-button-color: #b0bbbf; + --conduction-pagination-navigation-button-disabled-background-color: #ebebeb00; --conduction-pagination-navigation-button-disabled-color: #93a2a9; + --conduction-pagination-navigation-button-hover-background-color: #ebebeb00; --conduction-pagination-navigation-button-hover-color: #7eff9d; --conduction-pagination-page-hover-background-color: #141414; --conduction-pagination-page-hover-border-color: #71f892; @@ -834,6 +888,7 @@ body[data-themes*=dark] { --conduction-primary-top-nav-dropdown-hover-color: #9e9e9e; --conduction-primary-top-nav-hover-background-color: #141414; --conduction-primary-top-nav-hover-color: #7eff9d; + --conduction-secondary-top-nav-background-color: #ebebeb00; --conduction-secondary-top-nav-color: #b0bbbf; --conduction-secondary-top-nav-current-color: #b0bbbf; --conduction-secondary-top-nav-hover-background-color: #71f892; @@ -846,9 +901,12 @@ body[data-themes*=dark] { --conduction-tabs-scroll-button-color: #b0bbbf; --conduction-tabs-scroll-button-hover-background-color: #141414; --conduction-tabs-scroll-button-hover-color: #7eff9d; + --conduction-tabs-tab-background-color: #ebebeb00; --conduction-tabs-tab-color: #b0bbbf; + --conduction-tabs-tab-hover-background-color: #ebebeb00; --conduction-tabs-tab-hover-color: #7eff9d; --conduction-tabs-tab-list-border-bottom-color: #172227; + --conduction-tabs-tab-selected-background-color: #ebebeb00; --conduction-tabs-tab-selected-border-bottom-color: #71f892; --conduction-tabs-tab-selected-color: #b0bbbf; --conduction-tabs-tab-selected-hover-color: #9ad2ff; @@ -963,6 +1021,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #b0bbbf; --nldesign-component-heading-5-color: #b0bbbf; --nldesign-component-heading-6-color: #b0bbbf; + --nldesign-component-heading-color: #b0bbbf; --nldesign-component-link-color: #7eff9d; --nldesign-component-link-hover-color: #91ffc3; --nldesign-component-link-visited-color: #e88fbf; @@ -1063,6 +1122,8 @@ body[data-themes*=dark] { --tilburg-color-blue-400: #61c4f5; --tilburg-color-blue-500: #88c6f9; --tilburg-color-code-background-color: #172227; + --tilburg-color-dark-alpha-100: #ebebeb1a; + --tilburg-color-dark-alpha-50: #ebebeb0d; --tilburg-color-extra-limegreen: #85ba40; --tilburg-color-extra-purple: #8c6cc7; --tilburg-color-extra-yellow: #d3b70f; @@ -1081,6 +1142,8 @@ body[data-themes*=dark] { --tilburg-color-green-200: #3d7e70; --tilburg-color-green-300: #69f7d9; --tilburg-color-green-400: #7cf7db; + --tilburg-color-light-alpha-100: #1414141a; + --tilburg-color-light-alpha-50: #1414140d; --tilburg-color-orange-100: #492204; --tilburg-color-orange-200: #92470c; --tilburg-color-orange-300: #ed7719; @@ -1260,6 +1323,7 @@ body[data-themes*=dark] { --utrecht-button-secondary-action-focus-color: #77bbff; --utrecht-button-secondary-action-hover-background-color: #a7b2b6; --utrecht-button-secondary-action-hover-color: #77bbff; + --utrecht-button-subtle-active-background-color: #ebebeb1a; --utrecht-button-subtle-active-color: #77bbff; --utrecht-button-subtle-background-color: #141414; --utrecht-button-subtle-color: #7eff9d; @@ -1267,15 +1331,21 @@ body[data-themes*=dark] { --utrecht-button-subtle-disabled-color: #95a2a8; --utrecht-button-subtle-focus-background-color: #45b9f5; --utrecht-button-subtle-focus-color: #77bbff; + --utrecht-button-subtle-hover-background-color: #ebebeb0d; --utrecht-button-subtle-hover-color: #9fffcd; --utrecht-calendar-navigation-color: #7eff9d; --utrecht-calendar-navigation-label-color: #ebebeb; + --utrecht-calendar-table-days-item-day-active-border-color: #ebebeb00; + --utrecht-calendar-table-days-item-day-border-color: #ebebeb00; --utrecht-calendar-table-days-item-day-color: #ebebeb; + --utrecht-calendar-table-days-item-day-emphasis-border-color: #ebebeb00; + --utrecht-calendar-table-days-item-day-focus-border-color: #ebebeb00; --utrecht-calendar-table-days-item-day-hover-border-color: #ebebeb; --utrecht-calendar-table-days-item-day-hover-color: #7eff9d; --utrecht-calendar-table-days-item-day-is-today-background-color: #033b0e; --utrecht-calendar-table-days-item-day-is-today-border-color: #71f892; --utrecht-calendar-table-days-item-day-is-today-color: #7eff9d; + --utrecht-calendar-table-days-item-day-out-of-the-month-border-color: #ebebeb00; --utrecht-calendar-table-days-item-day-out-of-the-month-color: #8da7af; --utrecht-calendar-table-days-item-day-selected-background-color: #c23d76; --utrecht-calendar-table-days-item-day-selected-color: #9e9e9e; @@ -1426,8 +1496,10 @@ body[data-themes*=dark] { --utrecht-select-invalid-color: #c8dfff; --utrecht-separator-color: #172227; --utrecht-skip-link-background-color: #71f892; + --utrecht-skip-link-border-color: #ebebeb00; --utrecht-skip-link-color: #9e9e9e; --utrecht-skip-link-focus-background-color: #45b9f5; + --utrecht-skip-link-focus-border-color: #ebebeb00; --utrecht-skip-link-focus-color: #77bbff; --utrecht-spotlight-section-background-color: #0c2534; --utrecht-spotlight-section-border-color: #16445d; diff --git a/css/tokens/dark/provincie-zuid-holland.css b/css/tokens/dark/provincie-zuid-holland.css index be20af5a..11771862 100644 --- a/css/tokens/dark/provincie-zuid-holland.css +++ b/css/tokens/dark/provincie-zuid-holland.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/provincie-zuid-holland.css (sha256:abba1768e5c90f593b7c55f90c1b2a5a770f7d686f1e13a530834d5b2275bba7) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/provincie-zuid-holland.css (sha256:abba1768e5c90f593b7c55f90c1b2a5a770f7d686f1e13a530834d5b2275bba7) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --nldesign-color-background: #141414; @@ -15,7 +15,7 @@ --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -49,7 +49,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -71,6 +71,7 @@ --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -109,7 +110,7 @@ body[data-themes*=dark] { --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -143,7 +144,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -165,6 +166,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; diff --git a/css/tokens/dark/riddeliemers.css b/css/tokens/dark/riddeliemers.css index c548f973..b1208a1b 100644 --- a/css/tokens/dark/riddeliemers.css +++ b/css/tokens/dark/riddeliemers.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/riddeliemers.css (sha256:cccf22e0990ae869a3aaf7b033586ab167fdbe1968b3cc95f1b82923c19e5cbb) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/riddeliemers.css (sha256:cccf22e0990ae869a3aaf7b033586ab167fdbe1968b3cc95f1b82923c19e5cbb) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --nldesign-color-background: #141414; @@ -15,7 +15,7 @@ --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -48,7 +48,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -70,6 +70,7 @@ --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -107,7 +108,7 @@ body[data-themes*=dark] { --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -140,7 +141,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -162,6 +163,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; diff --git a/css/tokens/dark/ridderkerk.css b/css/tokens/dark/ridderkerk.css index 61823594..50acb54c 100644 --- a/css/tokens/dark/ridderkerk.css +++ b/css/tokens/dark/ridderkerk.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/ridderkerk.css (sha256:3caecde61aaf309b9f7620caefbb3378645e34c26b394d762b66605955eb4821) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/ridderkerk.css (sha256:3caecde61aaf309b9f7620caefbb3378645e34c26b394d762b66605955eb4821) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --nldesign-color-background: #141414; @@ -16,7 +16,7 @@ --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-green-20: #6bf7a3; --nldesign-color-grey-100: #141414; @@ -53,7 +53,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -75,6 +75,7 @@ --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -118,7 +119,7 @@ body[data-themes*=dark] { --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-green-20: #6bf7a3; --nldesign-color-grey-100: #141414; @@ -155,7 +156,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -177,6 +178,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; diff --git a/css/tokens/dark/rijkshuisstijl.css b/css/tokens/dark/rijkshuisstijl.css index a42bb71e..246e4a7f 100644 --- a/css/tokens/dark/rijkshuisstijl.css +++ b/css/tokens/dark/rijkshuisstijl.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/rijkshuisstijl.css (sha256:61bcf983a251e852d02a52fc737eab47eecb4878f22c453266f5b94c73c8bd3f) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/rijkshuisstijl.css (sha256:4817da72ea0de8ea7761c789d0f2d2e51fbc30746ceedb1bde94c6a85fb2b2bc) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --nldesign-color-background: #191c1f; @@ -15,7 +15,7 @@ --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -50,7 +50,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #9fbbe0; --nldesign-component-button-hover-color: #8cb9ea; @@ -72,6 +72,7 @@ --nldesign-component-heading-4-color: #d5ddf0; --nldesign-component-heading-5-color: #d5ddf0; --nldesign-component-heading-6-color: #d5ddf0; + --nldesign-component-heading-color: #d5ddf0; --nldesign-component-link-color: #64cbfe; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -124,7 +125,7 @@ body[data-themes*=dark] { --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -159,7 +160,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #9fbbe0; --nldesign-component-button-hover-color: #8cb9ea; @@ -181,6 +182,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #d5ddf0; --nldesign-component-heading-5-color: #d5ddf0; --nldesign-component-heading-6-color: #d5ddf0; + --nldesign-component-heading-color: #d5ddf0; --nldesign-component-link-color: #64cbfe; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; diff --git a/css/tokens/dark/rotterdam.css b/css/tokens/dark/rotterdam.css index 414a714a..83da9923 100644 --- a/css/tokens/dark/rotterdam.css +++ b/css/tokens/dark/rotterdam.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/rotterdam.css (sha256:f77d76e6ec1583b6b9fae571ee0de102e39291bf664927c6512f9cb7c270eae1) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/rotterdam.css (sha256:d87acd53c4d75b0012a76b4f5270d3f69e3b427fe223ea9197b570f0e65c5a65) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --ams-card-link-color: #5b99e4; @@ -371,7 +371,7 @@ --basis-color-selected-inverse-color-document: #141414; --basis-color-selected-inverse-color-hover: #141414; --basis-color-selected-inverse-color-subtle: #29415d; - --basis-color-transparent: #ebebeb; + --basis-color-transparent: #ebebeb00; --basis-color-warning-bg-active: #572d05; --basis-color-warning-bg-default: #422303; --basis-color-warning-bg-document: #2c1a02; @@ -428,7 +428,7 @@ --basis-form-control-invalid-color: #ff4848; --basis-form-control-placeholder-color: #9e9e9e; --basis-form-control-read-only-background-color: #1c1c1c; - --basis-form-control-read-only-border-color: #ebebeb; + --basis-form-control-read-only-border-color: #ebebeb00; --basis-form-control-read-only-color: #b0bbbf; --basis-heading-color: #b0bbbf; --conduction-primary-top-nav-background-color: #45b9f5; @@ -481,14 +481,14 @@ --denhaag-process-steps-step-marker-background-color: #171717; --denhaag-process-steps-step-marker-border-color: #7a7a7a; --denhaag-process-steps-step-marker-checked-background-color: #85f9b1; - --denhaag-process-steps-step-marker-checked-border-color: #ebebeb; + --denhaag-process-steps-step-marker-checked-border-color: #ebebeb00; --denhaag-process-steps-step-marker-checked-color: #141414; --denhaag-process-steps-step-marker-color: #a7b2b6; --denhaag-process-steps-step-marker-current-background-color: #a0a0a0; - --denhaag-process-steps-step-marker-current-border-color: #ebebeb; + --denhaag-process-steps-step-marker-current-border-color: #ebebeb00; --denhaag-process-steps-step-marker-current-color: #141414; --denhaag-process-steps-step-marker-warning-background-color: #e5ad73; - --denhaag-process-steps-step-marker-warning-border-color: #ebebeb; + --denhaag-process-steps-step-marker-warning-border-color: #ebebeb00; --denhaag-process-steps-step-marker-warning-color: #141414; --denhaag-process-steps-step-metaddata-color: #9e9e9e; --denhaag-process-steps-sub-step-heading-color: #b0bbbf; @@ -501,7 +501,7 @@ --denhaag-sidenav-link-color: #b0bbbf; --denhaag-sidenav-link-hover-color: #7eff9d; --denhaag-step-marker-checked-background-color: #85f9b1; - --denhaag-step-marker-checked-border-color: #ebebeb; + --denhaag-step-marker-checked-border-color: #ebebeb00; --denhaag-step-marker-checked-color: #141414; --denhaag-step-marker-connector-checked-outline-color: #5df696; --denhaag-step-marker-connector-default-outline-color: #7a7a7a; @@ -509,31 +509,31 @@ --denhaag-step-marker-connector-not-checked-outline-color: #464646; --denhaag-step-marker-connector-warning-outline-color: #e7953a; --denhaag-step-marker-current-background-color: #a0a0a0; - --denhaag-step-marker-current-border-color: #ebebeb; + --denhaag-step-marker-current-border-color: #ebebeb00; --denhaag-step-marker-current-color: #141414; --denhaag-step-marker-current-nested-color: #141414; --denhaag-step-marker-default-background-color: #a0a0a0; - --denhaag-step-marker-default-border-color: #ebebeb; + --denhaag-step-marker-default-border-color: #ebebeb00; --denhaag-step-marker-default-color: #141414; --denhaag-step-marker-default-nested-color: #a7b2b6; --denhaag-step-marker-disabled-background-color: #202020; --denhaag-step-marker-disabled-border-color: #464646; --denhaag-step-marker-disabled-color: #979797; --denhaag-step-marker-error-background-color: #f54646; - --denhaag-step-marker-error-border-color: #ebebeb; + --denhaag-step-marker-error-border-color: #ebebeb00; --denhaag-step-marker-error-color: #141414; --denhaag-step-marker-not-checked-background-color: #202020; --denhaag-step-marker-not-checked-border-color: #464646; --denhaag-step-marker-not-checked-color: #979797; --denhaag-step-marker-warning-background-color: #e5ad73; - --denhaag-step-marker-warning-border-color: #ebebeb; + --denhaag-step-marker-warning-border-color: #ebebeb00; --denhaag-step-marker-warning-color: #141414; --denhaag-step-marker-warning-nested-background-color: #e5ad73; - --denhaag-step-marker-warning-nested-border-color: #ebebeb; + --denhaag-step-marker-warning-nested-border-color: #ebebeb00; --lux-login-link-active-background-color: #2a2a2a; --lux-login-link-active-border-color: #8b8b8b; --lux-login-link-active-color: #7eb9ff; - --lux-login-link-background-color: #ebebeb; + --lux-login-link-background-color: #ebebeb00; --lux-login-link-border-color: #7a7a7a; --lux-login-link-color: #5b99e4; --lux-login-link-hover-background-color: #252525; @@ -623,6 +623,7 @@ --nldesign-component-heading-4-color: #b0bbbf; --nldesign-component-heading-5-color: #b0bbbf; --nldesign-component-heading-6-color: #b0bbbf; + --nldesign-component-heading-color: #b0bbbf; --nldesign-component-link-color: #ebebeb; --nldesign-component-link-hover-color: #7eff9d; --nldesign-component-link-visited-color: #e88fbf; @@ -819,6 +820,8 @@ --tilburg-color-blue-400: #3cb8f5; --tilburg-color-blue-500: #45b9f5; --tilburg-color-code-background-color: #172227; + --tilburg-color-dark-alpha-100: #ebebeb1a; + --tilburg-color-dark-alpha-50: #ebebeb0d; --tilburg-color-extra-limegreen: #59a954; --tilburg-color-extra-purple: #e084b5; --tilburg-color-extra-yellow: #c1b10c; @@ -837,6 +840,8 @@ --tilburg-color-green-200: #325f3e; --tilburg-color-green-300: #71f892; --tilburg-color-green-400: #80f8b7; + --tilburg-color-light-alpha-100: #1414141a; + --tilburg-color-light-alpha-50: #1414140d; --tilburg-color-orange-100: #392c05; --tilburg-color-orange-200: #67520d; --tilburg-color-orange-300: #b13825; @@ -925,7 +930,7 @@ --tilburg-status-badge-warning-background-color: #392c05; --tilburg-status-badge-warning-color: #e56a58; --todo-avatar-background-color: #5994db; - --todo-avatar-border-color: #ebebeb; + --todo-avatar-border-color: #ebebeb00; --todo-avatar-color: #9e9e9e; --todo-breadcrumb-nav-current-color: #9e9e9e; --todo-breadcrumb-nav-link-active-color: #7eb9ff; @@ -960,7 +965,7 @@ --todo-case-card-icon-color: #5b99e4; --todo-case-card-metadata-color: #b0bbbf; --todo-checkbox-checked-active-background-color: #71aff8; - --todo-checkbox-checked-active-border-color: #ebebeb; + --todo-checkbox-checked-active-border-color: #ebebeb00; --todo-checkbox-checked-active-color: #9e9e9e; --todo-checkbox-checked-disabled-background-color: #202020; --todo-checkbox-checked-disabled-border-color: #464646; @@ -969,10 +974,10 @@ --todo-checkbox-checked-focus-visible-border-color: #8b8b8b; --todo-checkbox-checked-focus-visible-color: #b0bbbf; --todo-checkbox-checked-hover-background-color: #5ea2f2; - --todo-checkbox-checked-hover-border-color: #ebebeb; + --todo-checkbox-checked-hover-border-color: #ebebeb00; --todo-checkbox-checked-hover-color: #9e9e9e; --todo-checkbox-indeterminate-active-background-color: #71aff8; - --todo-checkbox-indeterminate-active-border-color: #ebebeb; + --todo-checkbox-indeterminate-active-border-color: #ebebeb00; --todo-checkbox-indeterminate-active-color: #9e9e9e; --todo-checkbox-indeterminate-disabled-background-color: #202020; --todo-checkbox-indeterminate-disabled-border-color: #464646; @@ -981,15 +986,15 @@ --todo-checkbox-indeterminate-focus-visible-border-color: #8b8b8b; --todo-checkbox-indeterminate-focus-visible-color: #b0bbbf; --todo-checkbox-indeterminate-hover-background-color: #5ea2f2; - --todo-checkbox-indeterminate-hover-border-color: #ebebeb; + --todo-checkbox-indeterminate-hover-border-color: #ebebeb00; --todo-checkbox-indeterminate-hover-color: #9e9e9e; --todo-contact-timeline-step-header-toggle-focus-visible-background-color: #f2d40d; --todo-drawer-background-color: #171717; --todo-drawer-border-color: #464646; --todo-drawer-color: #b0bbbf; - --todo-drawer-footer-background-color: #ebebeb; + --todo-drawer-footer-background-color: #ebebeb00; --todo-drawer-footer-border-color: #464646; - --todo-drawer-header-background-color: #ebebeb; + --todo-drawer-header-background-color: #ebebeb00; --todo-drawer-header-border-color: #464646; --todo-drawer-header-label-color: #b0bbbf; --todo-form-field-label-color: #b0bbbf; @@ -999,78 +1004,78 @@ --todo-form-summary-item-border-color: #464646; --todo-form-summary-item-color: #b0bbbf; --todo-login-link-focus-visible-background-color: #f2d40d; - --todo-login-link-focus-visible-border-color: #ebebeb; + --todo-login-link-focus-visible-border-color: #ebebeb00; --todo-login-link-focus-visible-color: #eaebeb; --todo-modal-dialog-background-color: #171717; --todo-modal-dialog-border-color: #464646; --todo-modal-dialog-color: #b0bbbf; - --todo-modal-dialog-footer-background-color: #ebebeb; - --todo-modal-dialog-footer-border-color: #ebebeb; - --todo-modal-dialog-header-background-color: #ebebeb; + --todo-modal-dialog-footer-background-color: #ebebeb00; + --todo-modal-dialog-footer-border-color: #ebebeb00; + --todo-modal-dialog-header-background-color: #ebebeb00; --todo-modal-dialog-header-border-color: #464646; --todo-modal-dialog-header-label-color: #b0bbbf; --todo-pagination-description-color: #b0bbbf; --todo-pagination-ellipses-color: #9e9e9e; - --todo-pagination-page-link-active-background-color: #ebebeb; - --todo-pagination-page-link-active-border-color: #ebebeb; + --todo-pagination-page-link-active-background-color: #ebebeb00; + --todo-pagination-page-link-active-border-color: #ebebeb00; --todo-pagination-page-link-active-color: #7eb9ff; - --todo-pagination-page-link-background-color: #ebebeb; - --todo-pagination-page-link-border-color: #ebebeb; + --todo-pagination-page-link-background-color: #ebebeb00; + --todo-pagination-page-link-border-color: #ebebeb00; --todo-pagination-page-link-color: #5b99e4; --todo-pagination-page-link-current-background-color: #2a2a2a; - --todo-pagination-page-link-current-border-color: #ebebeb; + --todo-pagination-page-link-current-border-color: #ebebeb00; --todo-pagination-page-link-current-color: #b0bbbf; --todo-pagination-page-link-focus-visible-background-color: #f2d40d; --todo-pagination-page-link-focus-visible-color: #eaebeb; - --todo-pagination-page-link-hover-background-color: #ebebeb; - --todo-pagination-page-link-hover-border-color: #ebebeb; + --todo-pagination-page-link-hover-background-color: #ebebeb00; + --todo-pagination-page-link-hover-border-color: #ebebeb00; --todo-pagination-page-link-hover-color: #65aafb; - --todo-pagination-relative-link-active-background-color: #ebebeb; - --todo-pagination-relative-link-active-border-color: #ebebeb; + --todo-pagination-relative-link-active-background-color: #ebebeb00; + --todo-pagination-relative-link-active-border-color: #ebebeb00; --todo-pagination-relative-link-active-color: #7eb9ff; - --todo-pagination-relative-link-background-color: #ebebeb; - --todo-pagination-relative-link-border-color: #ebebeb; + --todo-pagination-relative-link-background-color: #ebebeb00; + --todo-pagination-relative-link-border-color: #ebebeb00; --todo-pagination-relative-link-color: #5b99e4; - --todo-pagination-relative-link-disabled-background-color: #ebebeb; - --todo-pagination-relative-link-disabled-border-color: #ebebeb; + --todo-pagination-relative-link-disabled-background-color: #ebebeb00; + --todo-pagination-relative-link-disabled-border-color: #ebebeb00; --todo-pagination-relative-link-disabled-color: #9e9e9e; --todo-pagination-relative-link-focus-visible-background-color: #f2d40d; --todo-pagination-relative-link-focus-visible-color: #eaebeb; - --todo-pagination-relative-link-hover-background-color: #ebebeb; - --todo-pagination-relative-link-hover-border-color: #ebebeb; + --todo-pagination-relative-link-hover-background-color: #ebebeb00; + --todo-pagination-relative-link-hover-border-color: #ebebeb00; --todo-pagination-relative-link-hover-color: #65aafb; - --todo-progress-list-button-checked-active-background-color: #ebebeb; - --todo-progress-list-button-checked-active-border-color: #ebebeb; + --todo-progress-list-button-checked-active-background-color: #ebebeb00; + --todo-progress-list-button-checked-active-border-color: #ebebeb00; --todo-progress-list-button-checked-active-color: #7eb9ff; - --todo-progress-list-button-checked-background-color: #ebebeb; - --todo-progress-list-button-checked-border-color: #ebebeb; + --todo-progress-list-button-checked-background-color: #ebebeb00; + --todo-progress-list-button-checked-border-color: #ebebeb00; --todo-progress-list-button-checked-focus-visible-background-color: #f2d40d; - --todo-progress-list-button-checked-focus-visible-border-color: #ebebeb; + --todo-progress-list-button-checked-focus-visible-border-color: #ebebeb00; --todo-progress-list-button-checked-focus-visible-color: #eaebeb; - --todo-progress-list-button-checked-hover-background-color: #ebebeb; - --todo-progress-list-button-checked-hover-border-color: #ebebeb; + --todo-progress-list-button-checked-hover-background-color: #ebebeb00; + --todo-progress-list-button-checked-hover-border-color: #ebebeb00; --todo-progress-list-button-checked-hover-color: #65aafb; - --todo-progress-list-button-current-active-background-color: #ebebeb; - --todo-progress-list-button-current-active-border-color: #ebebeb; + --todo-progress-list-button-current-active-background-color: #ebebeb00; + --todo-progress-list-button-current-active-border-color: #ebebeb00; --todo-progress-list-button-current-active-color: #7eb9ff; - --todo-progress-list-button-current-background-color: #ebebeb; - --todo-progress-list-button-current-border-color: #ebebeb; + --todo-progress-list-button-current-background-color: #ebebeb00; + --todo-progress-list-button-current-border-color: #ebebeb00; --todo-progress-list-button-current-focus-visible-background-color: #f2d40d; - --todo-progress-list-button-current-focus-visible-border-color: #ebebeb; + --todo-progress-list-button-current-focus-visible-border-color: #ebebeb00; --todo-progress-list-button-current-focus-visible-color: #eaebeb; - --todo-progress-list-button-current-hover-background-color: #ebebeb; - --todo-progress-list-button-current-hover-border-color: #ebebeb; + --todo-progress-list-button-current-hover-background-color: #ebebeb00; + --todo-progress-list-button-current-hover-border-color: #ebebeb00; --todo-progress-list-button-current-hover-color: #65aafb; - --todo-progress-list-button-error-active-background-color: #ebebeb; - --todo-progress-list-button-error-active-border-color: #ebebeb; + --todo-progress-list-button-error-active-background-color: #ebebeb00; + --todo-progress-list-button-error-active-border-color: #ebebeb00; --todo-progress-list-button-error-active-color: #7eb9ff; - --todo-progress-list-button-error-background-color: #ebebeb; - --todo-progress-list-button-error-border-color: #ebebeb; + --todo-progress-list-button-error-background-color: #ebebeb00; + --todo-progress-list-button-error-border-color: #ebebeb00; --todo-progress-list-button-error-focus-visible-background-color: #f2d40d; - --todo-progress-list-button-error-focus-visible-border-color: #ebebeb; + --todo-progress-list-button-error-focus-visible-border-color: #ebebeb00; --todo-progress-list-button-error-focus-visible-color: #eaebeb; - --todo-progress-list-button-error-hover-background-color: #ebebeb; - --todo-progress-list-button-error-hover-border-color: #ebebeb; + --todo-progress-list-button-error-hover-background-color: #ebebeb00; + --todo-progress-list-button-error-hover-border-color: #ebebeb00; --todo-progress-list-button-error-hover-color: #65aafb; --todo-progress-list-button-icon-checked-active-color: #7eb9ff; --todo-progress-list-button-icon-checked-color: #97ffbe; @@ -1088,16 +1093,16 @@ --todo-progress-list-button-icon-warning-color: #edb57b; --todo-progress-list-button-icon-warning-focus-visible-color: #eaebeb; --todo-progress-list-button-icon-warning-hover-color: #65aafb; - --todo-progress-list-button-warning-active-background-color: #ebebeb; - --todo-progress-list-button-warning-active-border-color: #ebebeb; + --todo-progress-list-button-warning-active-background-color: #ebebeb00; + --todo-progress-list-button-warning-active-border-color: #ebebeb00; --todo-progress-list-button-warning-active-color: #7eb9ff; - --todo-progress-list-button-warning-background-color: #ebebeb; - --todo-progress-list-button-warning-border-color: #ebebeb; + --todo-progress-list-button-warning-background-color: #ebebeb00; + --todo-progress-list-button-warning-border-color: #ebebeb00; --todo-progress-list-button-warning-focus-visible-background-color: #f2d40d; - --todo-progress-list-button-warning-focus-visible-border-color: #ebebeb; + --todo-progress-list-button-warning-focus-visible-border-color: #ebebeb00; --todo-progress-list-button-warning-focus-visible-color: #eaebeb; - --todo-progress-list-button-warning-hover-background-color: #ebebeb; - --todo-progress-list-button-warning-hover-border-color: #ebebeb; + --todo-progress-list-button-warning-hover-background-color: #ebebeb00; + --todo-progress-list-button-warning-hover-border-color: #ebebeb00; --todo-progress-list-button-warning-hover-color: #65aafb; --todo-progress-list-connector-checked-border-color: #5df696; --todo-progress-list-connector-error-border-color: #b71818; @@ -1109,19 +1114,19 @@ --todo-progress-list-step-heading-not-checked-color: #9e9e9e; --todo-progress-list-step-heading-warning-color: #edb57b; --todo-progress-list-step-marker-checked-background-color: #85f9b1; - --todo-progress-list-step-marker-checked-border-color: #ebebeb; + --todo-progress-list-step-marker-checked-border-color: #ebebeb00; --todo-progress-list-step-marker-checked-color: #141414; --todo-progress-list-step-marker-current-background-color: #cccccc; - --todo-progress-list-step-marker-current-border-color: #ebebeb; + --todo-progress-list-step-marker-current-border-color: #ebebeb00; --todo-progress-list-step-marker-current-color: #141414; --todo-progress-list-step-marker-error-background-color: #f54646; - --todo-progress-list-step-marker-error-border-color: #ebebeb; + --todo-progress-list-step-marker-error-border-color: #ebebeb00; --todo-progress-list-step-marker-error-color: #141414; --todo-progress-list-step-marker-not-checked-background-color: #171717; --todo-progress-list-step-marker-not-checked-border-color: #7a7a7a; --todo-progress-list-step-marker-not-checked-color: #979797; --todo-progress-list-step-marker-warning-background-color: #e5ad73; - --todo-progress-list-step-marker-warning-border-color: #ebebeb; + --todo-progress-list-step-marker-warning-border-color: #ebebeb00; --todo-progress-list-step-marker-warning-color: #141414; --todo-radio-button-checked-disabled-background-color: #202020; --todo-radio-button-checked-disabled-border-color: #464646; @@ -1130,12 +1135,12 @@ --todo-switch-icon-disabled-color: #9e9e9e; --todo-switch-icon-focus-visible-color: #eaebeb; --todo-switch-thumb-background-color: #141414; - --todo-switch-thumb-border-color: #ebebeb; + --todo-switch-thumb-border-color: #ebebeb00; --todo-switch-thumb-disabled-background-color: #141414; --todo-switch-thumb-focus-visible-background-color: #e0e2e2; --todo-switch-track-active-background-color: #b7b7b7; --todo-switch-track-background-color: #a0a0a0; - --todo-switch-track-border-color: #ebebeb; + --todo-switch-track-border-color: #ebebeb00; --todo-switch-track-checked-active-background-color: #9ffac1; --todo-switch-track-checked-background-color: #85f9b1; --todo-switch-track-checked-disabled-background-color: #464646; @@ -1154,25 +1159,25 @@ --todo-textbox-hover-border-color: #9e9e9e; --todo-textbox-hover-color: #b0bbbf; --utrecht-accordion-button-active-background-color: #2a2a2a; - --utrecht-accordion-button-active-border-color: #ebebeb; + --utrecht-accordion-button-active-border-color: #ebebeb00; --utrecht-accordion-button-active-color: #7eb9ff; --utrecht-accordion-button-background-color: #202020; - --utrecht-accordion-button-border-color: #ebebeb; + --utrecht-accordion-button-border-color: #ebebeb00; --utrecht-accordion-button-color: #5b99e4; --utrecht-accordion-button-expanded-background-color: #172227; --utrecht-accordion-button-expanded-color: #7eff9d; --utrecht-accordion-button-focus-background-color: #5ac1f6; --utrecht-accordion-button-focus-color: #7eff9d; --utrecht-accordion-button-focus-visible-background-color: #f2d40d; - --utrecht-accordion-button-focus-visible-border-color: #ebebeb; + --utrecht-accordion-button-focus-visible-border-color: #ebebeb00; --utrecht-accordion-button-focus-visible-color: #eaebeb; --utrecht-accordion-button-hover-background-color: #252525; - --utrecht-accordion-button-hover-border-color: #ebebeb; + --utrecht-accordion-button-hover-border-color: #ebebeb00; --utrecht-accordion-button-hover-color: #65aafb; - --utrecht-accordion-panel-border-color: #ebebeb; + --utrecht-accordion-panel-border-color: #ebebeb00; --utrecht-accordion-section-border-color: #464646; --utrecht-accordion-section-hover-border-color: #464646; - --utrecht-action-group-background-color: #ebebeb; + --utrecht-action-group-background-color: #ebebeb00; --utrecht-alert-background-color: #0c2534; --utrecht-alert-border-color: #16445d; --utrecht-alert-color: #b0bbbf; @@ -1212,8 +1217,8 @@ --utrecht-badge-counter-background-color: #71f892; --utrecht-badge-counter-color: #9e9e9e; --utrecht-blockquote-attribution-color: #9e9e9e; - --utrecht-blockquote-background-color: #ebebeb; - --utrecht-blockquote-border-color: #ebebeb; + --utrecht-blockquote-background-color: #ebebeb00; + --utrecht-blockquote-border-color: #ebebeb00; --utrecht-blockquote-color: #b0bbbf; --utrecht-blockquote-content-color: #b0bbbf; --utrecht-body-background-color: #141414; @@ -1263,6 +1268,7 @@ --utrecht-button-secondary-action-focus-color: #7eff9d; --utrecht-button-secondary-action-hover-background-color: #a7b2b6; --utrecht-button-secondary-action-hover-color: #7eff9d; + --utrecht-button-subtle-active-background-color: #ebebeb1a; --utrecht-button-subtle-active-color: #7eff9d; --utrecht-button-subtle-background-color: #141414; --utrecht-button-subtle-color: #7eff9d; @@ -1270,6 +1276,7 @@ --utrecht-button-subtle-disabled-color: #95a2a8; --utrecht-button-subtle-focus-background-color: #5ac1f6; --utrecht-button-subtle-focus-color: #7eff9d; + --utrecht-button-subtle-hover-background-color: #ebebeb0d; --utrecht-button-subtle-hover-color: #9fffcd; --utrecht-card-background-color: #141414; --utrecht-card-color: #b0bbbf; @@ -1389,22 +1396,22 @@ --utrecht-ordered-list-color: #b0bbbf; --utrecht-page-footer-background-color: #71f892; --utrecht-page-footer-color: #9e9e9e; - --utrecht-pagination-page-link-background-color: #ebebeb; - --utrecht-pagination-page-link-border-color: #ebebeb; + --utrecht-pagination-page-link-background-color: #ebebeb00; + --utrecht-pagination-page-link-border-color: #ebebeb00; --utrecht-pagination-page-link-color: #5b99e4; --utrecht-pagination-page-link-current-background-color: #2a2a2a; - --utrecht-pagination-page-link-current-border-color: #ebebeb; + --utrecht-pagination-page-link-current-border-color: #ebebeb00; --utrecht-pagination-page-link-current-color: #b0bbbf; --utrecht-pagination-page-link-hover-background-color: #252525; - --utrecht-pagination-page-link-hover-border-color: #ebebeb; + --utrecht-pagination-page-link-hover-border-color: #ebebeb00; --utrecht-pagination-page-link-hover-color: #65aafb; - --utrecht-pagination-relative-link-background-color: #ebebeb; - --utrecht-pagination-relative-link-border-color: #ebebeb; + --utrecht-pagination-relative-link-background-color: #ebebeb00; + --utrecht-pagination-relative-link-border-color: #ebebeb00; --utrecht-pagination-relative-link-color: #5b99e4; - --utrecht-pagination-relative-link-disabled-background-color: #ebebeb; + --utrecht-pagination-relative-link-disabled-background-color: #ebebeb00; --utrecht-pagination-relative-link-disabled-color: #9e9e9e; --utrecht-pagination-relative-link-hover-background-color: #252525; - --utrecht-pagination-relative-link-hover-border-color: #ebebeb; + --utrecht-pagination-relative-link-hover-border-color: #ebebeb00; --utrecht-pagination-relative-link-hover-color: #65aafb; --utrecht-paragraph-color: #b0bbbf; --utrecht-paragraph-lead-color: #b0bbbf; @@ -1416,10 +1423,10 @@ --utrecht-radio-button-background-color: #171717; --utrecht-radio-button-border-color: #7a7a7a; --utrecht-radio-button-checked-active-background-color: #71aff8; - --utrecht-radio-button-checked-active-border-color: #ebebeb; + --utrecht-radio-button-checked-active-border-color: #ebebeb00; --utrecht-radio-button-checked-active-color: #9e9e9e; --utrecht-radio-button-checked-background-color: #5994db; - --utrecht-radio-button-checked-border-color: #ebebeb; + --utrecht-radio-button-checked-border-color: #ebebeb00; --utrecht-radio-button-checked-color: #9e9e9e; --utrecht-radio-button-checked-disabled-background-color: #172227; --utrecht-radio-button-checked-disabled-border-color: #606f75; @@ -1428,7 +1435,7 @@ --utrecht-radio-button-checked-focus-border-color: #8b8b8b; --utrecht-radio-button-checked-focus-color: #b0bbbf; --utrecht-radio-button-checked-hover-background-color: #5ea2f2; - --utrecht-radio-button-checked-hover-border-color: #ebebeb; + --utrecht-radio-button-checked-hover-border-color: #ebebeb00; --utrecht-radio-button-checked-hover-color: #9e9e9e; --utrecht-radio-button-color: #9e9e9e; --utrecht-radio-button-disabled-background-color: #202020; @@ -1931,7 +1938,7 @@ body[data-themes*=dark] { --basis-color-selected-inverse-color-document: #141414; --basis-color-selected-inverse-color-hover: #141414; --basis-color-selected-inverse-color-subtle: #29415d; - --basis-color-transparent: #ebebeb; + --basis-color-transparent: #ebebeb00; --basis-color-warning-bg-active: #572d05; --basis-color-warning-bg-default: #422303; --basis-color-warning-bg-document: #2c1a02; @@ -1988,7 +1995,7 @@ body[data-themes*=dark] { --basis-form-control-invalid-color: #ff4848; --basis-form-control-placeholder-color: #9e9e9e; --basis-form-control-read-only-background-color: #1c1c1c; - --basis-form-control-read-only-border-color: #ebebeb; + --basis-form-control-read-only-border-color: #ebebeb00; --basis-form-control-read-only-color: #b0bbbf; --basis-heading-color: #b0bbbf; --conduction-primary-top-nav-background-color: #45b9f5; @@ -2041,14 +2048,14 @@ body[data-themes*=dark] { --denhaag-process-steps-step-marker-background-color: #171717; --denhaag-process-steps-step-marker-border-color: #7a7a7a; --denhaag-process-steps-step-marker-checked-background-color: #85f9b1; - --denhaag-process-steps-step-marker-checked-border-color: #ebebeb; + --denhaag-process-steps-step-marker-checked-border-color: #ebebeb00; --denhaag-process-steps-step-marker-checked-color: #141414; --denhaag-process-steps-step-marker-color: #a7b2b6; --denhaag-process-steps-step-marker-current-background-color: #a0a0a0; - --denhaag-process-steps-step-marker-current-border-color: #ebebeb; + --denhaag-process-steps-step-marker-current-border-color: #ebebeb00; --denhaag-process-steps-step-marker-current-color: #141414; --denhaag-process-steps-step-marker-warning-background-color: #e5ad73; - --denhaag-process-steps-step-marker-warning-border-color: #ebebeb; + --denhaag-process-steps-step-marker-warning-border-color: #ebebeb00; --denhaag-process-steps-step-marker-warning-color: #141414; --denhaag-process-steps-step-metaddata-color: #9e9e9e; --denhaag-process-steps-sub-step-heading-color: #b0bbbf; @@ -2061,7 +2068,7 @@ body[data-themes*=dark] { --denhaag-sidenav-link-color: #b0bbbf; --denhaag-sidenav-link-hover-color: #7eff9d; --denhaag-step-marker-checked-background-color: #85f9b1; - --denhaag-step-marker-checked-border-color: #ebebeb; + --denhaag-step-marker-checked-border-color: #ebebeb00; --denhaag-step-marker-checked-color: #141414; --denhaag-step-marker-connector-checked-outline-color: #5df696; --denhaag-step-marker-connector-default-outline-color: #7a7a7a; @@ -2069,31 +2076,31 @@ body[data-themes*=dark] { --denhaag-step-marker-connector-not-checked-outline-color: #464646; --denhaag-step-marker-connector-warning-outline-color: #e7953a; --denhaag-step-marker-current-background-color: #a0a0a0; - --denhaag-step-marker-current-border-color: #ebebeb; + --denhaag-step-marker-current-border-color: #ebebeb00; --denhaag-step-marker-current-color: #141414; --denhaag-step-marker-current-nested-color: #141414; --denhaag-step-marker-default-background-color: #a0a0a0; - --denhaag-step-marker-default-border-color: #ebebeb; + --denhaag-step-marker-default-border-color: #ebebeb00; --denhaag-step-marker-default-color: #141414; --denhaag-step-marker-default-nested-color: #a7b2b6; --denhaag-step-marker-disabled-background-color: #202020; --denhaag-step-marker-disabled-border-color: #464646; --denhaag-step-marker-disabled-color: #979797; --denhaag-step-marker-error-background-color: #f54646; - --denhaag-step-marker-error-border-color: #ebebeb; + --denhaag-step-marker-error-border-color: #ebebeb00; --denhaag-step-marker-error-color: #141414; --denhaag-step-marker-not-checked-background-color: #202020; --denhaag-step-marker-not-checked-border-color: #464646; --denhaag-step-marker-not-checked-color: #979797; --denhaag-step-marker-warning-background-color: #e5ad73; - --denhaag-step-marker-warning-border-color: #ebebeb; + --denhaag-step-marker-warning-border-color: #ebebeb00; --denhaag-step-marker-warning-color: #141414; --denhaag-step-marker-warning-nested-background-color: #e5ad73; - --denhaag-step-marker-warning-nested-border-color: #ebebeb; + --denhaag-step-marker-warning-nested-border-color: #ebebeb00; --lux-login-link-active-background-color: #2a2a2a; --lux-login-link-active-border-color: #8b8b8b; --lux-login-link-active-color: #7eb9ff; - --lux-login-link-background-color: #ebebeb; + --lux-login-link-background-color: #ebebeb00; --lux-login-link-border-color: #7a7a7a; --lux-login-link-color: #5b99e4; --lux-login-link-hover-background-color: #252525; @@ -2183,6 +2190,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #b0bbbf; --nldesign-component-heading-5-color: #b0bbbf; --nldesign-component-heading-6-color: #b0bbbf; + --nldesign-component-heading-color: #b0bbbf; --nldesign-component-link-color: #ebebeb; --nldesign-component-link-hover-color: #7eff9d; --nldesign-component-link-visited-color: #e88fbf; @@ -2379,6 +2387,8 @@ body[data-themes*=dark] { --tilburg-color-blue-400: #3cb8f5; --tilburg-color-blue-500: #45b9f5; --tilburg-color-code-background-color: #172227; + --tilburg-color-dark-alpha-100: #ebebeb1a; + --tilburg-color-dark-alpha-50: #ebebeb0d; --tilburg-color-extra-limegreen: #59a954; --tilburg-color-extra-purple: #e084b5; --tilburg-color-extra-yellow: #c1b10c; @@ -2397,6 +2407,8 @@ body[data-themes*=dark] { --tilburg-color-green-200: #325f3e; --tilburg-color-green-300: #71f892; --tilburg-color-green-400: #80f8b7; + --tilburg-color-light-alpha-100: #1414141a; + --tilburg-color-light-alpha-50: #1414140d; --tilburg-color-orange-100: #392c05; --tilburg-color-orange-200: #67520d; --tilburg-color-orange-300: #b13825; @@ -2485,7 +2497,7 @@ body[data-themes*=dark] { --tilburg-status-badge-warning-background-color: #392c05; --tilburg-status-badge-warning-color: #e56a58; --todo-avatar-background-color: #5994db; - --todo-avatar-border-color: #ebebeb; + --todo-avatar-border-color: #ebebeb00; --todo-avatar-color: #9e9e9e; --todo-breadcrumb-nav-current-color: #9e9e9e; --todo-breadcrumb-nav-link-active-color: #7eb9ff; @@ -2520,7 +2532,7 @@ body[data-themes*=dark] { --todo-case-card-icon-color: #5b99e4; --todo-case-card-metadata-color: #b0bbbf; --todo-checkbox-checked-active-background-color: #71aff8; - --todo-checkbox-checked-active-border-color: #ebebeb; + --todo-checkbox-checked-active-border-color: #ebebeb00; --todo-checkbox-checked-active-color: #9e9e9e; --todo-checkbox-checked-disabled-background-color: #202020; --todo-checkbox-checked-disabled-border-color: #464646; @@ -2529,10 +2541,10 @@ body[data-themes*=dark] { --todo-checkbox-checked-focus-visible-border-color: #8b8b8b; --todo-checkbox-checked-focus-visible-color: #b0bbbf; --todo-checkbox-checked-hover-background-color: #5ea2f2; - --todo-checkbox-checked-hover-border-color: #ebebeb; + --todo-checkbox-checked-hover-border-color: #ebebeb00; --todo-checkbox-checked-hover-color: #9e9e9e; --todo-checkbox-indeterminate-active-background-color: #71aff8; - --todo-checkbox-indeterminate-active-border-color: #ebebeb; + --todo-checkbox-indeterminate-active-border-color: #ebebeb00; --todo-checkbox-indeterminate-active-color: #9e9e9e; --todo-checkbox-indeterminate-disabled-background-color: #202020; --todo-checkbox-indeterminate-disabled-border-color: #464646; @@ -2541,15 +2553,15 @@ body[data-themes*=dark] { --todo-checkbox-indeterminate-focus-visible-border-color: #8b8b8b; --todo-checkbox-indeterminate-focus-visible-color: #b0bbbf; --todo-checkbox-indeterminate-hover-background-color: #5ea2f2; - --todo-checkbox-indeterminate-hover-border-color: #ebebeb; + --todo-checkbox-indeterminate-hover-border-color: #ebebeb00; --todo-checkbox-indeterminate-hover-color: #9e9e9e; --todo-contact-timeline-step-header-toggle-focus-visible-background-color: #f2d40d; --todo-drawer-background-color: #171717; --todo-drawer-border-color: #464646; --todo-drawer-color: #b0bbbf; - --todo-drawer-footer-background-color: #ebebeb; + --todo-drawer-footer-background-color: #ebebeb00; --todo-drawer-footer-border-color: #464646; - --todo-drawer-header-background-color: #ebebeb; + --todo-drawer-header-background-color: #ebebeb00; --todo-drawer-header-border-color: #464646; --todo-drawer-header-label-color: #b0bbbf; --todo-form-field-label-color: #b0bbbf; @@ -2559,78 +2571,78 @@ body[data-themes*=dark] { --todo-form-summary-item-border-color: #464646; --todo-form-summary-item-color: #b0bbbf; --todo-login-link-focus-visible-background-color: #f2d40d; - --todo-login-link-focus-visible-border-color: #ebebeb; + --todo-login-link-focus-visible-border-color: #ebebeb00; --todo-login-link-focus-visible-color: #eaebeb; --todo-modal-dialog-background-color: #171717; --todo-modal-dialog-border-color: #464646; --todo-modal-dialog-color: #b0bbbf; - --todo-modal-dialog-footer-background-color: #ebebeb; - --todo-modal-dialog-footer-border-color: #ebebeb; - --todo-modal-dialog-header-background-color: #ebebeb; + --todo-modal-dialog-footer-background-color: #ebebeb00; + --todo-modal-dialog-footer-border-color: #ebebeb00; + --todo-modal-dialog-header-background-color: #ebebeb00; --todo-modal-dialog-header-border-color: #464646; --todo-modal-dialog-header-label-color: #b0bbbf; --todo-pagination-description-color: #b0bbbf; --todo-pagination-ellipses-color: #9e9e9e; - --todo-pagination-page-link-active-background-color: #ebebeb; - --todo-pagination-page-link-active-border-color: #ebebeb; + --todo-pagination-page-link-active-background-color: #ebebeb00; + --todo-pagination-page-link-active-border-color: #ebebeb00; --todo-pagination-page-link-active-color: #7eb9ff; - --todo-pagination-page-link-background-color: #ebebeb; - --todo-pagination-page-link-border-color: #ebebeb; + --todo-pagination-page-link-background-color: #ebebeb00; + --todo-pagination-page-link-border-color: #ebebeb00; --todo-pagination-page-link-color: #5b99e4; --todo-pagination-page-link-current-background-color: #2a2a2a; - --todo-pagination-page-link-current-border-color: #ebebeb; + --todo-pagination-page-link-current-border-color: #ebebeb00; --todo-pagination-page-link-current-color: #b0bbbf; --todo-pagination-page-link-focus-visible-background-color: #f2d40d; --todo-pagination-page-link-focus-visible-color: #eaebeb; - --todo-pagination-page-link-hover-background-color: #ebebeb; - --todo-pagination-page-link-hover-border-color: #ebebeb; + --todo-pagination-page-link-hover-background-color: #ebebeb00; + --todo-pagination-page-link-hover-border-color: #ebebeb00; --todo-pagination-page-link-hover-color: #65aafb; - --todo-pagination-relative-link-active-background-color: #ebebeb; - --todo-pagination-relative-link-active-border-color: #ebebeb; + --todo-pagination-relative-link-active-background-color: #ebebeb00; + --todo-pagination-relative-link-active-border-color: #ebebeb00; --todo-pagination-relative-link-active-color: #7eb9ff; - --todo-pagination-relative-link-background-color: #ebebeb; - --todo-pagination-relative-link-border-color: #ebebeb; + --todo-pagination-relative-link-background-color: #ebebeb00; + --todo-pagination-relative-link-border-color: #ebebeb00; --todo-pagination-relative-link-color: #5b99e4; - --todo-pagination-relative-link-disabled-background-color: #ebebeb; - --todo-pagination-relative-link-disabled-border-color: #ebebeb; + --todo-pagination-relative-link-disabled-background-color: #ebebeb00; + --todo-pagination-relative-link-disabled-border-color: #ebebeb00; --todo-pagination-relative-link-disabled-color: #9e9e9e; --todo-pagination-relative-link-focus-visible-background-color: #f2d40d; --todo-pagination-relative-link-focus-visible-color: #eaebeb; - --todo-pagination-relative-link-hover-background-color: #ebebeb; - --todo-pagination-relative-link-hover-border-color: #ebebeb; + --todo-pagination-relative-link-hover-background-color: #ebebeb00; + --todo-pagination-relative-link-hover-border-color: #ebebeb00; --todo-pagination-relative-link-hover-color: #65aafb; - --todo-progress-list-button-checked-active-background-color: #ebebeb; - --todo-progress-list-button-checked-active-border-color: #ebebeb; + --todo-progress-list-button-checked-active-background-color: #ebebeb00; + --todo-progress-list-button-checked-active-border-color: #ebebeb00; --todo-progress-list-button-checked-active-color: #7eb9ff; - --todo-progress-list-button-checked-background-color: #ebebeb; - --todo-progress-list-button-checked-border-color: #ebebeb; + --todo-progress-list-button-checked-background-color: #ebebeb00; + --todo-progress-list-button-checked-border-color: #ebebeb00; --todo-progress-list-button-checked-focus-visible-background-color: #f2d40d; - --todo-progress-list-button-checked-focus-visible-border-color: #ebebeb; + --todo-progress-list-button-checked-focus-visible-border-color: #ebebeb00; --todo-progress-list-button-checked-focus-visible-color: #eaebeb; - --todo-progress-list-button-checked-hover-background-color: #ebebeb; - --todo-progress-list-button-checked-hover-border-color: #ebebeb; + --todo-progress-list-button-checked-hover-background-color: #ebebeb00; + --todo-progress-list-button-checked-hover-border-color: #ebebeb00; --todo-progress-list-button-checked-hover-color: #65aafb; - --todo-progress-list-button-current-active-background-color: #ebebeb; - --todo-progress-list-button-current-active-border-color: #ebebeb; + --todo-progress-list-button-current-active-background-color: #ebebeb00; + --todo-progress-list-button-current-active-border-color: #ebebeb00; --todo-progress-list-button-current-active-color: #7eb9ff; - --todo-progress-list-button-current-background-color: #ebebeb; - --todo-progress-list-button-current-border-color: #ebebeb; + --todo-progress-list-button-current-background-color: #ebebeb00; + --todo-progress-list-button-current-border-color: #ebebeb00; --todo-progress-list-button-current-focus-visible-background-color: #f2d40d; - --todo-progress-list-button-current-focus-visible-border-color: #ebebeb; + --todo-progress-list-button-current-focus-visible-border-color: #ebebeb00; --todo-progress-list-button-current-focus-visible-color: #eaebeb; - --todo-progress-list-button-current-hover-background-color: #ebebeb; - --todo-progress-list-button-current-hover-border-color: #ebebeb; + --todo-progress-list-button-current-hover-background-color: #ebebeb00; + --todo-progress-list-button-current-hover-border-color: #ebebeb00; --todo-progress-list-button-current-hover-color: #65aafb; - --todo-progress-list-button-error-active-background-color: #ebebeb; - --todo-progress-list-button-error-active-border-color: #ebebeb; + --todo-progress-list-button-error-active-background-color: #ebebeb00; + --todo-progress-list-button-error-active-border-color: #ebebeb00; --todo-progress-list-button-error-active-color: #7eb9ff; - --todo-progress-list-button-error-background-color: #ebebeb; - --todo-progress-list-button-error-border-color: #ebebeb; + --todo-progress-list-button-error-background-color: #ebebeb00; + --todo-progress-list-button-error-border-color: #ebebeb00; --todo-progress-list-button-error-focus-visible-background-color: #f2d40d; - --todo-progress-list-button-error-focus-visible-border-color: #ebebeb; + --todo-progress-list-button-error-focus-visible-border-color: #ebebeb00; --todo-progress-list-button-error-focus-visible-color: #eaebeb; - --todo-progress-list-button-error-hover-background-color: #ebebeb; - --todo-progress-list-button-error-hover-border-color: #ebebeb; + --todo-progress-list-button-error-hover-background-color: #ebebeb00; + --todo-progress-list-button-error-hover-border-color: #ebebeb00; --todo-progress-list-button-error-hover-color: #65aafb; --todo-progress-list-button-icon-checked-active-color: #7eb9ff; --todo-progress-list-button-icon-checked-color: #97ffbe; @@ -2648,16 +2660,16 @@ body[data-themes*=dark] { --todo-progress-list-button-icon-warning-color: #edb57b; --todo-progress-list-button-icon-warning-focus-visible-color: #eaebeb; --todo-progress-list-button-icon-warning-hover-color: #65aafb; - --todo-progress-list-button-warning-active-background-color: #ebebeb; - --todo-progress-list-button-warning-active-border-color: #ebebeb; + --todo-progress-list-button-warning-active-background-color: #ebebeb00; + --todo-progress-list-button-warning-active-border-color: #ebebeb00; --todo-progress-list-button-warning-active-color: #7eb9ff; - --todo-progress-list-button-warning-background-color: #ebebeb; - --todo-progress-list-button-warning-border-color: #ebebeb; + --todo-progress-list-button-warning-background-color: #ebebeb00; + --todo-progress-list-button-warning-border-color: #ebebeb00; --todo-progress-list-button-warning-focus-visible-background-color: #f2d40d; - --todo-progress-list-button-warning-focus-visible-border-color: #ebebeb; + --todo-progress-list-button-warning-focus-visible-border-color: #ebebeb00; --todo-progress-list-button-warning-focus-visible-color: #eaebeb; - --todo-progress-list-button-warning-hover-background-color: #ebebeb; - --todo-progress-list-button-warning-hover-border-color: #ebebeb; + --todo-progress-list-button-warning-hover-background-color: #ebebeb00; + --todo-progress-list-button-warning-hover-border-color: #ebebeb00; --todo-progress-list-button-warning-hover-color: #65aafb; --todo-progress-list-connector-checked-border-color: #5df696; --todo-progress-list-connector-error-border-color: #b71818; @@ -2669,19 +2681,19 @@ body[data-themes*=dark] { --todo-progress-list-step-heading-not-checked-color: #9e9e9e; --todo-progress-list-step-heading-warning-color: #edb57b; --todo-progress-list-step-marker-checked-background-color: #85f9b1; - --todo-progress-list-step-marker-checked-border-color: #ebebeb; + --todo-progress-list-step-marker-checked-border-color: #ebebeb00; --todo-progress-list-step-marker-checked-color: #141414; --todo-progress-list-step-marker-current-background-color: #cccccc; - --todo-progress-list-step-marker-current-border-color: #ebebeb; + --todo-progress-list-step-marker-current-border-color: #ebebeb00; --todo-progress-list-step-marker-current-color: #141414; --todo-progress-list-step-marker-error-background-color: #f54646; - --todo-progress-list-step-marker-error-border-color: #ebebeb; + --todo-progress-list-step-marker-error-border-color: #ebebeb00; --todo-progress-list-step-marker-error-color: #141414; --todo-progress-list-step-marker-not-checked-background-color: #171717; --todo-progress-list-step-marker-not-checked-border-color: #7a7a7a; --todo-progress-list-step-marker-not-checked-color: #979797; --todo-progress-list-step-marker-warning-background-color: #e5ad73; - --todo-progress-list-step-marker-warning-border-color: #ebebeb; + --todo-progress-list-step-marker-warning-border-color: #ebebeb00; --todo-progress-list-step-marker-warning-color: #141414; --todo-radio-button-checked-disabled-background-color: #202020; --todo-radio-button-checked-disabled-border-color: #464646; @@ -2690,12 +2702,12 @@ body[data-themes*=dark] { --todo-switch-icon-disabled-color: #9e9e9e; --todo-switch-icon-focus-visible-color: #eaebeb; --todo-switch-thumb-background-color: #141414; - --todo-switch-thumb-border-color: #ebebeb; + --todo-switch-thumb-border-color: #ebebeb00; --todo-switch-thumb-disabled-background-color: #141414; --todo-switch-thumb-focus-visible-background-color: #e0e2e2; --todo-switch-track-active-background-color: #b7b7b7; --todo-switch-track-background-color: #a0a0a0; - --todo-switch-track-border-color: #ebebeb; + --todo-switch-track-border-color: #ebebeb00; --todo-switch-track-checked-active-background-color: #9ffac1; --todo-switch-track-checked-background-color: #85f9b1; --todo-switch-track-checked-disabled-background-color: #464646; @@ -2714,25 +2726,25 @@ body[data-themes*=dark] { --todo-textbox-hover-border-color: #9e9e9e; --todo-textbox-hover-color: #b0bbbf; --utrecht-accordion-button-active-background-color: #2a2a2a; - --utrecht-accordion-button-active-border-color: #ebebeb; + --utrecht-accordion-button-active-border-color: #ebebeb00; --utrecht-accordion-button-active-color: #7eb9ff; --utrecht-accordion-button-background-color: #202020; - --utrecht-accordion-button-border-color: #ebebeb; + --utrecht-accordion-button-border-color: #ebebeb00; --utrecht-accordion-button-color: #5b99e4; --utrecht-accordion-button-expanded-background-color: #172227; --utrecht-accordion-button-expanded-color: #7eff9d; --utrecht-accordion-button-focus-background-color: #5ac1f6; --utrecht-accordion-button-focus-color: #7eff9d; --utrecht-accordion-button-focus-visible-background-color: #f2d40d; - --utrecht-accordion-button-focus-visible-border-color: #ebebeb; + --utrecht-accordion-button-focus-visible-border-color: #ebebeb00; --utrecht-accordion-button-focus-visible-color: #eaebeb; --utrecht-accordion-button-hover-background-color: #252525; - --utrecht-accordion-button-hover-border-color: #ebebeb; + --utrecht-accordion-button-hover-border-color: #ebebeb00; --utrecht-accordion-button-hover-color: #65aafb; - --utrecht-accordion-panel-border-color: #ebebeb; + --utrecht-accordion-panel-border-color: #ebebeb00; --utrecht-accordion-section-border-color: #464646; --utrecht-accordion-section-hover-border-color: #464646; - --utrecht-action-group-background-color: #ebebeb; + --utrecht-action-group-background-color: #ebebeb00; --utrecht-alert-background-color: #0c2534; --utrecht-alert-border-color: #16445d; --utrecht-alert-color: #b0bbbf; @@ -2772,8 +2784,8 @@ body[data-themes*=dark] { --utrecht-badge-counter-background-color: #71f892; --utrecht-badge-counter-color: #9e9e9e; --utrecht-blockquote-attribution-color: #9e9e9e; - --utrecht-blockquote-background-color: #ebebeb; - --utrecht-blockquote-border-color: #ebebeb; + --utrecht-blockquote-background-color: #ebebeb00; + --utrecht-blockquote-border-color: #ebebeb00; --utrecht-blockquote-color: #b0bbbf; --utrecht-blockquote-content-color: #b0bbbf; --utrecht-body-background-color: #141414; @@ -2823,6 +2835,7 @@ body[data-themes*=dark] { --utrecht-button-secondary-action-focus-color: #7eff9d; --utrecht-button-secondary-action-hover-background-color: #a7b2b6; --utrecht-button-secondary-action-hover-color: #7eff9d; + --utrecht-button-subtle-active-background-color: #ebebeb1a; --utrecht-button-subtle-active-color: #7eff9d; --utrecht-button-subtle-background-color: #141414; --utrecht-button-subtle-color: #7eff9d; @@ -2830,6 +2843,7 @@ body[data-themes*=dark] { --utrecht-button-subtle-disabled-color: #95a2a8; --utrecht-button-subtle-focus-background-color: #5ac1f6; --utrecht-button-subtle-focus-color: #7eff9d; + --utrecht-button-subtle-hover-background-color: #ebebeb0d; --utrecht-button-subtle-hover-color: #9fffcd; --utrecht-card-background-color: #141414; --utrecht-card-color: #b0bbbf; @@ -2949,22 +2963,22 @@ body[data-themes*=dark] { --utrecht-ordered-list-color: #b0bbbf; --utrecht-page-footer-background-color: #71f892; --utrecht-page-footer-color: #9e9e9e; - --utrecht-pagination-page-link-background-color: #ebebeb; - --utrecht-pagination-page-link-border-color: #ebebeb; + --utrecht-pagination-page-link-background-color: #ebebeb00; + --utrecht-pagination-page-link-border-color: #ebebeb00; --utrecht-pagination-page-link-color: #5b99e4; --utrecht-pagination-page-link-current-background-color: #2a2a2a; - --utrecht-pagination-page-link-current-border-color: #ebebeb; + --utrecht-pagination-page-link-current-border-color: #ebebeb00; --utrecht-pagination-page-link-current-color: #b0bbbf; --utrecht-pagination-page-link-hover-background-color: #252525; - --utrecht-pagination-page-link-hover-border-color: #ebebeb; + --utrecht-pagination-page-link-hover-border-color: #ebebeb00; --utrecht-pagination-page-link-hover-color: #65aafb; - --utrecht-pagination-relative-link-background-color: #ebebeb; - --utrecht-pagination-relative-link-border-color: #ebebeb; + --utrecht-pagination-relative-link-background-color: #ebebeb00; + --utrecht-pagination-relative-link-border-color: #ebebeb00; --utrecht-pagination-relative-link-color: #5b99e4; - --utrecht-pagination-relative-link-disabled-background-color: #ebebeb; + --utrecht-pagination-relative-link-disabled-background-color: #ebebeb00; --utrecht-pagination-relative-link-disabled-color: #9e9e9e; --utrecht-pagination-relative-link-hover-background-color: #252525; - --utrecht-pagination-relative-link-hover-border-color: #ebebeb; + --utrecht-pagination-relative-link-hover-border-color: #ebebeb00; --utrecht-pagination-relative-link-hover-color: #65aafb; --utrecht-paragraph-color: #b0bbbf; --utrecht-paragraph-lead-color: #b0bbbf; @@ -2976,10 +2990,10 @@ body[data-themes*=dark] { --utrecht-radio-button-background-color: #171717; --utrecht-radio-button-border-color: #7a7a7a; --utrecht-radio-button-checked-active-background-color: #71aff8; - --utrecht-radio-button-checked-active-border-color: #ebebeb; + --utrecht-radio-button-checked-active-border-color: #ebebeb00; --utrecht-radio-button-checked-active-color: #9e9e9e; --utrecht-radio-button-checked-background-color: #5994db; - --utrecht-radio-button-checked-border-color: #ebebeb; + --utrecht-radio-button-checked-border-color: #ebebeb00; --utrecht-radio-button-checked-color: #9e9e9e; --utrecht-radio-button-checked-disabled-background-color: #172227; --utrecht-radio-button-checked-disabled-border-color: #606f75; @@ -2988,7 +3002,7 @@ body[data-themes*=dark] { --utrecht-radio-button-checked-focus-border-color: #8b8b8b; --utrecht-radio-button-checked-focus-color: #b0bbbf; --utrecht-radio-button-checked-hover-background-color: #5ea2f2; - --utrecht-radio-button-checked-hover-border-color: #ebebeb; + --utrecht-radio-button-checked-hover-border-color: #ebebeb00; --utrecht-radio-button-checked-hover-color: #9e9e9e; --utrecht-radio-button-color: #9e9e9e; --utrecht-radio-button-disabled-background-color: #202020; diff --git a/css/tokens/dark/stedebroec.css b/css/tokens/dark/stedebroec.css index c750711b..5420f322 100644 --- a/css/tokens/dark/stedebroec.css +++ b/css/tokens/dark/stedebroec.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/stedebroec.css (sha256:1ba2544f657b44eecd0031cdd3662636e659fed5f35f2add1bb8ebdd0c07630a) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/stedebroec.css (sha256:1ba2544f657b44eecd0031cdd3662636e659fed5f35f2add1bb8ebdd0c07630a) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --nldesign-color-background: #141414; @@ -15,7 +15,7 @@ --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -48,7 +48,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -70,6 +70,7 @@ --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -107,7 +108,7 @@ body[data-themes*=dark] { --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -140,7 +141,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -162,6 +163,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; diff --git a/css/tokens/dark/summer-breeze.css b/css/tokens/dark/summer-breeze.css index 616786ee..cd7953e3 100644 --- a/css/tokens/dark/summer-breeze.css +++ b/css/tokens/dark/summer-breeze.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/summer-breeze.css (sha256:f63c9a43783e85e3ea2500ec110f61e4f2b12877f43479723545275531be7acb) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/summer-breeze.css (sha256:f63c9a43783e85e3ea2500ec110f61e4f2b12877f43479723545275531be7acb) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --nldesign-color-background: #0c1e32; @@ -15,7 +15,7 @@ --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -48,7 +48,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -70,6 +70,7 @@ --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -98,7 +99,7 @@ --summer-color-error: #cc5144; --summer-color-error-border: #4e1b16; --summer-color-error-light: #370f0b; - --summer-color-focus: #367ace; + --summer-color-focus: #367ace80; --summer-color-info: #508cd7; --summer-color-info-border: #123152; --summer-color-info-light: #0d243a; @@ -137,7 +138,7 @@ body[data-themes*=dark] { --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -170,7 +171,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -192,6 +193,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -220,7 +222,7 @@ body[data-themes*=dark] { --summer-color-error: #cc5144; --summer-color-error-border: #4e1b16; --summer-color-error-light: #370f0b; - --summer-color-focus: #367ace; + --summer-color-focus: #367ace80; --summer-color-info: #508cd7; --summer-color-info-border: #123152; --summer-color-info-light: #0d243a; diff --git a/css/tokens/dark/tilburg.css b/css/tokens/dark/tilburg.css index e3b78433..35cd503a 100644 --- a/css/tokens/dark/tilburg.css +++ b/css/tokens/dark/tilburg.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/tilburg.css (sha256:0b00378efa52b730db7527a2e084394bd8b2f79b0727aa596821396448e4f963) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/tilburg.css (sha256:0b00378efa52b730db7527a2e084394bd8b2f79b0727aa596821396448e4f963) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --nldesign-color-background: #141414; @@ -15,7 +15,7 @@ --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -48,7 +48,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -70,6 +70,7 @@ --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -107,7 +108,7 @@ body[data-themes*=dark] { --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -140,7 +141,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -162,6 +163,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; diff --git a/css/tokens/dark/tubbergen.css b/css/tokens/dark/tubbergen.css index 5f6ab4dc..5ed9ed89 100644 --- a/css/tokens/dark/tubbergen.css +++ b/css/tokens/dark/tubbergen.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/tubbergen.css (sha256:c88867eee452eb2cade75a4076adf15a8a973017c80466685cdb38fbd6940c8f) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/tubbergen.css (sha256:c88867eee452eb2cade75a4076adf15a8a973017c80466685cdb38fbd6940c8f) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --nldesign-color-alert-error-background: #420f13; @@ -27,7 +27,7 @@ --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 229, 66, 47; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-green-11: #b3f2cc; --nldesign-color-green-16: #a0f0c0; @@ -71,6 +71,8 @@ --nldesign-color-white-100: #141414; --nldesign-color-white-98: #191919; --nldesign-color-yellow-0: #f3bf19; + --nldesign-component-badge-background-color: #81afe2; + --nldesign-component-badge-color: #9e9e9e; --nldesign-component-button-active-background-color: #172d41; --nldesign-component-button-active-color: #8cb9ea; --nldesign-component-button-border-color: #81afe2; @@ -78,7 +80,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -100,6 +102,7 @@ --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -183,7 +186,7 @@ body[data-themes*=dark] { --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 229, 66, 47; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-green-11: #b3f2cc; --nldesign-color-green-16: #a0f0c0; @@ -227,6 +230,8 @@ body[data-themes*=dark] { --nldesign-color-white-100: #141414; --nldesign-color-white-98: #191919; --nldesign-color-yellow-0: #f3bf19; + --nldesign-component-badge-background-color: #81afe2; + --nldesign-component-badge-color: #9e9e9e; --nldesign-component-button-active-background-color: #172d41; --nldesign-component-button-active-color: #8cb9ea; --nldesign-component-button-border-color: #81afe2; @@ -234,7 +239,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -256,6 +261,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; diff --git a/css/tokens/dark/utrecht.css b/css/tokens/dark/utrecht.css index 48481ec7..d4e3c58d 100644 --- a/css/tokens/dark/utrecht.css +++ b/css/tokens/dark/utrecht.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/utrecht.css (sha256:2a9d792f6b88d96d2bf5735d0b2ac6270a47ee423bfafe0f0a1fd796adc69e0b) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/utrecht.css (sha256:2a9d792f6b88d96d2bf5735d0b2ac6270a47ee423bfafe0f0a1fd796adc69e0b) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --nldesign-color-background: #141414; @@ -15,7 +15,7 @@ --nldesign-color-error-hover: #f65656; --nldesign-color-error-rgb: 244, 53, 53; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #f43535; --nldesign-color-header-text: #9e9e9e; @@ -48,7 +48,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #16212e; --nldesign-component-button-hover-border-color: #7da9d8; --nldesign-component-button-hover-color: #70a3db; @@ -70,6 +70,7 @@ --nldesign-component-heading-4-color: #ebebeb; --nldesign-component-heading-5-color: #ebebeb; --nldesign-component-heading-6-color: #ebebeb; + --nldesign-component-heading-color: #ebebeb; --nldesign-component-link-color: #70a3db; --nldesign-component-link-hover-color: #85b1e0; --nldesign-component-link-visited-color: #985fdd; @@ -114,7 +115,7 @@ body[data-themes*=dark] { --nldesign-color-error-hover: #f65656; --nldesign-color-error-rgb: 244, 53, 53; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #f43535; --nldesign-color-header-text: #9e9e9e; @@ -147,7 +148,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #16212e; --nldesign-component-button-hover-border-color: #7da9d8; --nldesign-component-button-hover-color: #70a3db; @@ -169,6 +170,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #ebebeb; --nldesign-component-heading-5-color: #ebebeb; --nldesign-component-heading-6-color: #ebebeb; + --nldesign-component-heading-color: #ebebeb; --nldesign-component-link-color: #70a3db; --nldesign-component-link-hover-color: #85b1e0; --nldesign-component-link-visited-color: #985fdd; diff --git a/css/tokens/dark/venray.css b/css/tokens/dark/venray.css index 56d085a5..570caba2 100644 --- a/css/tokens/dark/venray.css +++ b/css/tokens/dark/venray.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/venray.css (sha256:0b5c805e218e073f0f313fb60f491154323f6e33eeb313a1bfbfed980de9894a) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/venray.css (sha256:0b5c805e218e073f0f313fb60f491154323f6e33eeb313a1bfbfed980de9894a) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --nldesign-color-background: #141414; @@ -15,7 +15,7 @@ --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -48,7 +48,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -70,6 +70,7 @@ --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -107,7 +108,7 @@ body[data-themes*=dark] { --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -140,7 +141,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -162,6 +163,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; diff --git a/css/tokens/dark/vng.css b/css/tokens/dark/vng.css index f19520a1..13bf3466 100644 --- a/css/tokens/dark/vng.css +++ b/css/tokens/dark/vng.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/vng.css (sha256:2cee2f5033da8d67573c846d3860f967f688a49540e7bcf1f2e09f3c2ce0da61) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/vng.css (sha256:2cee2f5033da8d67573c846d3860f967f688a49540e7bcf1f2e09f3c2ce0da61) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --ams-date-input-background-color: #141414; @@ -96,7 +96,7 @@ --nldesign-color-error-hover: #cc2c24; --nldesign-color-error-rgb: 227, 74, 66; --nldesign-color-favorite: #ed7719; - --nldesign-color-focus: #8c6cc7; + --nldesign-color-focus: #8c6cc780; --nldesign-color-focus-rgb: 140, 108, 199; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -129,7 +129,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #8c6cc7; + --nldesign-component-button-focus-border-color: #8c6cc780; --nldesign-component-button-hover-background-color: #03273b; --nldesign-component-button-hover-border-color: #61c4f5; --nldesign-component-button-hover-color: #9ad2ff; @@ -151,6 +151,7 @@ --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #77bbff; --nldesign-component-link-hover-color: #77bbff; --nldesign-component-link-visited-color: #906fcf; @@ -178,6 +179,8 @@ --tilburg-color-blue-400: #61c4f5; --tilburg-color-blue-500: #88c6f9; --tilburg-color-code-background-color: #242424; + --tilburg-color-dark-alpha-100: #ebebeb1a; + --tilburg-color-dark-alpha-50: #ebebeb0d; --tilburg-color-extra-limegreen: #85ba40; --tilburg-color-extra-purple: #8c6cc7; --tilburg-color-extra-yellow: #d3b70f; @@ -196,6 +199,8 @@ --tilburg-color-green-200: #3d7e70; --tilburg-color-green-300: #69f7d9; --tilburg-color-green-400: #7cf7db; + --tilburg-color-light-alpha-100: #1414141a; + --tilburg-color-light-alpha-50: #1414140d; --tilburg-color-orange-100: #492204; --tilburg-color-orange-200: #92470c; --tilburg-color-orange-300: #ed7719; @@ -366,12 +371,14 @@ --utrecht-button-secondary-action-hover-background-color: #17171b; --utrecht-button-secondary-action-hover-border-color: #6cb1f7; --utrecht-button-secondary-action-hover-color: #77bbff; + --utrecht-button-subtle-active-background-color: #ebebeb1a; --utrecht-button-subtle-active-color: #77bbff; --utrecht-button-subtle-color: #77bbff; --utrecht-button-subtle-disabled-background-color: #17171b; --utrecht-button-subtle-disabled-color: #8e9cae; --utrecht-button-subtle-focus-background-color: #42b1f3; --utrecht-button-subtle-focus-color: #77bbff; + --utrecht-button-subtle-hover-background-color: #ebebeb0d; --utrecht-button-subtle-hover-color: #77bbff; --utrecht-checkbox-active-background-color: #191e22; --utrecht-checkbox-active-border-color: #637186; @@ -663,7 +670,7 @@ body[data-themes*=dark] { --nldesign-color-error-hover: #cc2c24; --nldesign-color-error-rgb: 227, 74, 66; --nldesign-color-favorite: #ed7719; - --nldesign-color-focus: #8c6cc7; + --nldesign-color-focus: #8c6cc780; --nldesign-color-focus-rgb: 140, 108, 199; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -696,7 +703,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #8c6cc7; + --nldesign-component-button-focus-border-color: #8c6cc780; --nldesign-component-button-hover-background-color: #03273b; --nldesign-component-button-hover-border-color: #61c4f5; --nldesign-component-button-hover-color: #9ad2ff; @@ -718,6 +725,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #77bbff; --nldesign-component-link-hover-color: #77bbff; --nldesign-component-link-visited-color: #906fcf; @@ -745,6 +753,8 @@ body[data-themes*=dark] { --tilburg-color-blue-400: #61c4f5; --tilburg-color-blue-500: #88c6f9; --tilburg-color-code-background-color: #242424; + --tilburg-color-dark-alpha-100: #ebebeb1a; + --tilburg-color-dark-alpha-50: #ebebeb0d; --tilburg-color-extra-limegreen: #85ba40; --tilburg-color-extra-purple: #8c6cc7; --tilburg-color-extra-yellow: #d3b70f; @@ -763,6 +773,8 @@ body[data-themes*=dark] { --tilburg-color-green-200: #3d7e70; --tilburg-color-green-300: #69f7d9; --tilburg-color-green-400: #7cf7db; + --tilburg-color-light-alpha-100: #1414141a; + --tilburg-color-light-alpha-50: #1414140d; --tilburg-color-orange-100: #492204; --tilburg-color-orange-200: #92470c; --tilburg-color-orange-300: #ed7719; @@ -933,12 +945,14 @@ body[data-themes*=dark] { --utrecht-button-secondary-action-hover-background-color: #17171b; --utrecht-button-secondary-action-hover-border-color: #6cb1f7; --utrecht-button-secondary-action-hover-color: #77bbff; + --utrecht-button-subtle-active-background-color: #ebebeb1a; --utrecht-button-subtle-active-color: #77bbff; --utrecht-button-subtle-color: #77bbff; --utrecht-button-subtle-disabled-background-color: #17171b; --utrecht-button-subtle-disabled-color: #8e9cae; --utrecht-button-subtle-focus-background-color: #42b1f3; --utrecht-button-subtle-focus-color: #77bbff; + --utrecht-button-subtle-hover-background-color: #ebebeb0d; --utrecht-button-subtle-hover-color: #77bbff; --utrecht-checkbox-active-background-color: #191e22; --utrecht-checkbox-active-border-color: #637186; diff --git a/css/tokens/dark/vught.css b/css/tokens/dark/vught.css index 654ac309..e4b3dd5d 100644 --- a/css/tokens/dark/vught.css +++ b/css/tokens/dark/vught.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/vught.css (sha256:f444e375d1942be94daa39d7e7d41d1ad7c477950593daac05158a8917b6bfa1) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/vught.css (sha256:f444e375d1942be94daa39d7e7d41d1ad7c477950593daac05158a8917b6bfa1) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --nldesign-color-background: #141414; @@ -15,7 +15,7 @@ --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -48,7 +48,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -70,6 +70,7 @@ --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -107,7 +108,7 @@ body[data-themes*=dark] { --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -140,7 +141,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -162,6 +163,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; diff --git a/css/tokens/dark/westervoort.css b/css/tokens/dark/westervoort.css index 7b0c6992..7fdc9c87 100644 --- a/css/tokens/dark/westervoort.css +++ b/css/tokens/dark/westervoort.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/westervoort.css (sha256:a181011169bc6e71204edeb0453752c44442c31f37a2561e3dbf60e3ecec6141) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/westervoort.css (sha256:a181011169bc6e71204edeb0453752c44442c31f37a2561e3dbf60e3ecec6141) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --nldesign-color-background: #141414; @@ -15,7 +15,7 @@ --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -46,7 +46,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -68,6 +68,7 @@ --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -105,7 +106,7 @@ body[data-themes*=dark] { --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -136,7 +137,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -158,6 +159,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; diff --git a/css/tokens/dark/xxllnc.css b/css/tokens/dark/xxllnc.css index 7bfda451..064a6b25 100644 --- a/css/tokens/dark/xxllnc.css +++ b/css/tokens/dark/xxllnc.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/xxllnc.css (sha256:fc0d7b2fdcec0ccfaa57e618a408ec49a666dbc80ded7af6f002799a9bed9336) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/xxllnc.css (sha256:fc0d7b2fdcec0ccfaa57e618a408ec49a666dbc80ded7af6f002799a9bed9336) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --nldesign-color-alert-error: #db838b; @@ -12,10 +12,13 @@ --nldesign-color-background-darker: #3a3b3c; --nldesign-color-background-hover: #252728; --nldesign-color-black-0: #ebebeb; + --nldesign-color-black-0-22t: #ebebeb38; --nldesign-color-black-10: #d5d5d5; + --nldesign-color-black-12-6-7t: #cfcfd210; --nldesign-color-black-14: #caccd1; --nldesign-color-black-16: #c5c5ca; --nldesign-color-black-17: #c6c6c6; + --nldesign-color-black-17-hover: #c6c6c6cc; --nldesign-color-black-21: #bbbbc0; --nldesign-color-black-23: #b6b5bc; --nldesign-color-black-6: #dddbe1; @@ -30,13 +33,14 @@ --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 196, 44, 58; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-grey-31: #a8a8a8; --nldesign-color-grey-43: #8f8f8f; --nldesign-color-grey-46: #878787; --nldesign-color-grey-82: #3b3b3b; --nldesign-color-grey-90: #2a2a2a; + --nldesign-color-grey-90-hover: #2a2a2acc; --nldesign-color-grey-92: #262626; --nldesign-color-grey-97: #1b1b1b; --nldesign-color-header-background: #141414; @@ -67,6 +71,8 @@ --nldesign-color-white-100: #141414; --nldesign-color-white-100-0t: #141414; --nldesign-color-white-95: #202020; + --nldesign-component-badge-background-color: #ebebeb; + --nldesign-component-badge-color: #9e9e9e; --nldesign-component-button-active-background-color: #172d41; --nldesign-component-button-active-color: #ebebeb; --nldesign-component-button-border-color: #ebebeb; @@ -74,7 +80,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #c0c0c0; --nldesign-component-button-hover-color: #ebebeb; @@ -96,6 +102,7 @@ --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -122,10 +129,13 @@ --xxllnc-color-alert-warning: #f3d16f; --xxllnc-color-alert-warning-background: #4f3d04; --xxllnc-color-black-0: #ebebeb; + --xxllnc-color-black-0-22t: #ebebeb38; --xxllnc-color-black-10: #d5d5d5; + --xxllnc-color-black-12-6-7t: #cfcfd210; --xxllnc-color-black-14: #caccd1; --xxllnc-color-black-16: #c5c5ca; --xxllnc-color-black-17: #c6c6c6; + --xxllnc-color-black-17-hover: #c6c6c6cc; --xxllnc-color-black-21: #bbbbc0; --xxllnc-color-black-23: #b6b5bc; --xxllnc-color-black-6: #dddbe1; @@ -136,6 +146,7 @@ --xxllnc-color-grey-46: #878787; --xxllnc-color-grey-82: #3b3b3b; --xxllnc-color-grey-90: #2a2a2a; + --xxllnc-color-grey-90-hover: #2a2a2acc; --xxllnc-color-grey-92: #262626; --xxllnc-color-grey-97: #1b1b1b; --xxllnc-color-lightgrey-86: #333333; @@ -163,10 +174,13 @@ body[data-themes*=dark] { --nldesign-color-background-darker: #3a3b3c; --nldesign-color-background-hover: #252728; --nldesign-color-black-0: #ebebeb; + --nldesign-color-black-0-22t: #ebebeb38; --nldesign-color-black-10: #d5d5d5; + --nldesign-color-black-12-6-7t: #cfcfd210; --nldesign-color-black-14: #caccd1; --nldesign-color-black-16: #c5c5ca; --nldesign-color-black-17: #c6c6c6; + --nldesign-color-black-17-hover: #c6c6c6cc; --nldesign-color-black-21: #bbbbc0; --nldesign-color-black-23: #b6b5bc; --nldesign-color-black-6: #dddbe1; @@ -181,13 +195,14 @@ body[data-themes*=dark] { --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 196, 44, 58; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-grey-31: #a8a8a8; --nldesign-color-grey-43: #8f8f8f; --nldesign-color-grey-46: #878787; --nldesign-color-grey-82: #3b3b3b; --nldesign-color-grey-90: #2a2a2a; + --nldesign-color-grey-90-hover: #2a2a2acc; --nldesign-color-grey-92: #262626; --nldesign-color-grey-97: #1b1b1b; --nldesign-color-header-background: #141414; @@ -218,6 +233,8 @@ body[data-themes*=dark] { --nldesign-color-white-100: #141414; --nldesign-color-white-100-0t: #141414; --nldesign-color-white-95: #202020; + --nldesign-component-badge-background-color: #ebebeb; + --nldesign-component-badge-color: #9e9e9e; --nldesign-component-button-active-background-color: #172d41; --nldesign-component-button-active-color: #ebebeb; --nldesign-component-button-border-color: #ebebeb; @@ -225,7 +242,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #c0c0c0; --nldesign-component-button-hover-color: #ebebeb; @@ -247,6 +264,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -273,10 +291,13 @@ body[data-themes*=dark] { --xxllnc-color-alert-warning: #f3d16f; --xxllnc-color-alert-warning-background: #4f3d04; --xxllnc-color-black-0: #ebebeb; + --xxllnc-color-black-0-22t: #ebebeb38; --xxllnc-color-black-10: #d5d5d5; + --xxllnc-color-black-12-6-7t: #cfcfd210; --xxllnc-color-black-14: #caccd1; --xxllnc-color-black-16: #c5c5ca; --xxllnc-color-black-17: #c6c6c6; + --xxllnc-color-black-17-hover: #c6c6c6cc; --xxllnc-color-black-21: #bbbbc0; --xxllnc-color-black-23: #b6b5bc; --xxllnc-color-black-6: #dddbe1; @@ -287,6 +308,7 @@ body[data-themes*=dark] { --xxllnc-color-grey-46: #878787; --xxllnc-color-grey-82: #3b3b3b; --xxllnc-color-grey-90: #2a2a2a; + --xxllnc-color-grey-90-hover: #2a2a2acc; --xxllnc-color-grey-92: #262626; --xxllnc-color-grey-97: #1b1b1b; --xxllnc-color-lightgrey-86: #333333; diff --git a/css/tokens/dark/zevenaar.css b/css/tokens/dark/zevenaar.css index 3c3cca6c..6c5ace28 100644 --- a/css/tokens/dark/zevenaar.css +++ b/css/tokens/dark/zevenaar.css @@ -1,4 +1,4 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/zevenaar.css (sha256:47873ebf10fc8d2a550d7c5385f00b399560b625a48e0bf7c68d61b2e6bb811b) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/zevenaar.css (sha256:47873ebf10fc8d2a550d7c5385f00b399560b625a48e0bf7c68d61b2e6bb811b) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --nldesign-color-background: #141414; @@ -15,7 +15,7 @@ --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -48,7 +48,7 @@ --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -70,6 +70,7 @@ --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; @@ -107,7 +108,7 @@ body[data-themes*=dark] { --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; + --nldesign-color-focus: #39adf580; --nldesign-color-focus-rgb: 57, 173, 245; --nldesign-color-header-background: #141414; --nldesign-color-header-text: #cccccc; @@ -140,7 +141,7 @@ body[data-themes*=dark] { --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; + --nldesign-component-button-focus-border-color: #39adf580; --nldesign-component-button-hover-background-color: #112131; --nldesign-component-button-hover-border-color: #6397da; --nldesign-component-button-hover-color: #8cb9ea; @@ -162,6 +163,7 @@ body[data-themes*=dark] { --nldesign-component-heading-4-color: #cccccc; --nldesign-component-heading-5-color: #cccccc; --nldesign-component-heading-6-color: #cccccc; + --nldesign-component-heading-color: #cccccc; --nldesign-component-link-color: #8cb9ea; --nldesign-component-link-hover-color: #3db5ff; --nldesign-component-link-visited-color: #cea0eb; diff --git a/css/tokens/dark/zwolle.css b/css/tokens/dark/zwolle.css index 086d16ca..049e27fc 100644 --- a/css/tokens/dark/zwolle.css +++ b/css/tokens/dark/zwolle.css @@ -1,184 +1,200 @@ -/* GENERATED by nldesign DarkPaletteService v2 from tokens/zwolle.css (sha256:99957cc6439a88e53e3454f34b73a965db49361aebcc7badc573b76b24482203) — do not edit */ +/* GENERATED by nldesign DarkPaletteService v3 from tokens/zwolle.css (sha256:1c267a3957b857146fffba4c8802e4948540ba5be6e9cad2f76399c16178fc0d) — do not edit */ @media (prefers-color-scheme: dark) { body:not([data-theme-light]):not([data-theme-dark]):not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast]) { --nldesign-color-background: #141414; - --nldesign-color-background-dark: #2c2e2f; - --nldesign-color-background-darker: #3a3b3c; - --nldesign-color-background-hover: #252728; - --nldesign-color-border: #535353; - --nldesign-color-border-dark: #929292; - --nldesign-color-button-primary-background: #81afe2; - --nldesign-color-button-primary-border: #81afe2; - --nldesign-color-button-primary-hover: #6397da; + --nldesign-color-background-dark: #252525; + --nldesign-color-background-darker: #373737; + --nldesign-color-background-hover: #1d1d1d; + --nldesign-color-border: #252525; + --nldesign-color-border-dark: #878787; + --nldesign-color-button-primary-background: #4d6fb2; + --nldesign-color-button-primary-border: #4d6fb2; + --nldesign-color-button-primary-hover: #6b87bf; --nldesign-color-button-primary-text: #9e9e9e; --nldesign-color-error: #d83d32; --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; - --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; - --nldesign-color-focus-rgb: 57, 173, 245; - --nldesign-color-header-background: #141414; - --nldesign-color-header-text: #cccccc; - --nldesign-color-info: #39adf5; - --nldesign-color-info-rgb: 57, 173, 245; - --nldesign-color-link: #81afe2; - --nldesign-color-link-hover: #39adf5; - --nldesign-color-link-visited: #c392e3; + --nldesign-color-favorite: #f321a5; + --nldesign-color-focus: #ebebeb80; + --nldesign-color-focus-rgb: 235, 235, 235; + --nldesign-color-header-background: #4d6fb2; + --nldesign-color-header-text: #9e9e9e; + --nldesign-color-info: #4c8cc4; + --nldesign-color-info-rgb: 76, 140, 196; + --nldesign-color-link: #4d6fb2; + --nldesign-color-link-hover: #6b87bf; + --nldesign-color-link-visited: #4c8cc4; --nldesign-color-nav-background: #141414; - --nldesign-color-placeholder-dark: #3f3f3f; - --nldesign-color-placeholder-light: #2a2a2a; - --nldesign-color-primary: #2f80d9; - --nldesign-color-primary-hover: #1d5499; - --nldesign-color-primary-light: #112131; - --nldesign-color-primary-light-hover: #172d41; - --nldesign-color-primary-text: #111111; + --nldesign-color-placeholder-dark: #373737; + --nldesign-color-placeholder-light: #252525; + --nldesign-color-primary: #476db8; + --nldesign-color-primary-hover: #395793; + --nldesign-color-primary-light: #151f34; + --nldesign-color-primary-light-hover: #1e2d4b; + --nldesign-color-primary-text: #fefefe; --nldesign-color-success: #9ceb6f; --nldesign-color-success-rgb: 156, 235, 111; - --nldesign-color-text: #cccccc; + --nldesign-color-text: #ebebeb; --nldesign-color-text-light: #9e9e9e; --nldesign-color-text-muted: #9e9e9e; --nldesign-color-warning: #f48c25; --nldesign-color-warning-rgb: 244, 140, 37; - --nldesign-component-badge-background-color: #81afe2; + --nldesign-component-badge-background-color: #4d6fb2; --nldesign-component-badge-color: #9e9e9e; - --nldesign-component-button-active-background-color: #172d41; - --nldesign-component-button-active-color: #8cb9ea; - --nldesign-component-button-border-color: #81afe2; - --nldesign-component-button-color: #8cb9ea; + --nldesign-component-button-active-background-color: #1e2d4b; + --nldesign-component-button-active-color: #7390c9; + --nldesign-component-button-border-color: #4d6fb2; + --nldesign-component-button-color: #7390c9; --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; - --nldesign-component-button-hover-background-color: #112131; - --nldesign-component-button-hover-border-color: #6397da; - --nldesign-component-button-hover-color: #8cb9ea; - --nldesign-component-button-primary-action-background-color: #81afe2; - --nldesign-component-button-primary-action-border-color: #81afe2; + --nldesign-component-button-focus-border-color: #ebebeb80; + --nldesign-component-button-hover-background-color: #151f34; + --nldesign-component-button-hover-border-color: #6b87bf; + --nldesign-component-button-hover-color: #7390c9; + --nldesign-component-button-primary-action-background-color: #4d6fb2; + --nldesign-component-button-primary-action-border-color: #4d6fb2; --nldesign-component-button-primary-action-color: #9e9e9e; - --nldesign-component-button-primary-action-hover-background-color: #6397da; + --nldesign-component-button-primary-action-hover-background-color: #6b87bf; --nldesign-component-button-primary-action-hover-color: #9e9e9e; - --nldesign-component-button-secondary-action-border-color: #81afe2; - --nldesign-component-button-secondary-action-color: #8cb9ea; - --nldesign-component-button-secondary-action-hover-background-color: #112131; - --nldesign-component-form-field-label-color: #cccccc; - --nldesign-component-form-fieldset-border-color: #535353; - --nldesign-component-form-select-border-color: #535353; - --nldesign-component-form-select-focus-border-color: #39adf5; - --nldesign-component-heading-1-color: #cccccc; - --nldesign-component-heading-2-color: #cccccc; - --nldesign-component-heading-3-color: #cccccc; - --nldesign-component-heading-4-color: #cccccc; - --nldesign-component-heading-5-color: #cccccc; - --nldesign-component-heading-6-color: #cccccc; - --nldesign-component-link-color: #8cb9ea; - --nldesign-component-link-hover-color: #3db5ff; - --nldesign-component-link-visited-color: #cea0eb; - --nldesign-component-ordered-list-color: #cccccc; - --nldesign-component-paragraph-color: #cccccc; - --nldesign-component-separator-border-color: #535353; - --nldesign-component-table-border-color: #535353; - --nldesign-component-table-header-background-color: #2c2e2f; - --nldesign-component-table-header-color: #cccccc; - --nldesign-component-table-row-hover-background-color: #252728; + --nldesign-component-button-secondary-action-border-color: #4d6fb2; + --nldesign-component-button-secondary-action-color: #7390c9; + --nldesign-component-button-secondary-action-hover-background-color: #151f34; + --nldesign-component-form-field-label-color: #ebebeb; + --nldesign-component-form-fieldset-border-color: #252525; + --nldesign-component-form-select-border-color: #252525; + --nldesign-component-form-select-focus-border-color: #4c8cc4; + --nldesign-component-heading-1-color: #ebebeb; + --nldesign-component-heading-2-color: #ebebeb; + --nldesign-component-heading-3-color: #ebebeb; + --nldesign-component-heading-4-color: #ebebeb; + --nldesign-component-heading-5-color: #ebebeb; + --nldesign-component-heading-6-color: #ebebeb; + --nldesign-component-heading-color: #ebebeb; + --nldesign-component-link-color: #7390c9; + --nldesign-component-link-hover-color: #7390c9; + --nldesign-component-link-visited-color: #68a2d4; + --nldesign-component-ordered-list-color: #ebebeb; + --nldesign-component-paragraph-color: #ebebeb; + --nldesign-component-separator-border-color: #252525; + --nldesign-component-table-border-color: #252525; + --nldesign-component-table-header-background-color: #252525; + --nldesign-component-table-header-color: #ebebeb; + --nldesign-component-table-row-hover-background-color: #1d1d1d; --nldesign-component-textbox-background-color: #9e9e9e; --nldesign-component-textbox-border-color: #9e9e9e; - --nldesign-component-textbox-color: #cccccc; + --nldesign-component-textbox-color: #ebebeb; --nldesign-component-textbox-disabled-background-color: #9e9e9e; --nldesign-component-textbox-disabled-color: #9e9e9e; - --nldesign-component-textbox-focus-border-color: #3db5ff; + --nldesign-component-textbox-focus-border-color: #68a2d4; --nldesign-component-textbox-hover-border-color: #9e9e9e; --nldesign-component-textbox-invalid-border-color: #e75f55; - --nldesign-component-unordered-list-color: #cccccc; + --nldesign-component-unordered-list-color: #ebebeb; + --zwolle-color-black: #ebebeb; + --zwolle-color-blue-40: #6b87bf; + --zwolle-color-blue-45: #4c8cc4; + --zwolle-color-blue-50: #4d6fb2; + --zwolle-color-grey: #252525; + --zwolle-color-pink-50: #f321a5; + --zwolle-color-white: #141414; } } body[data-theme-dark], body[data-themes*=dark] { --nldesign-color-background: #141414; - --nldesign-color-background-dark: #2c2e2f; - --nldesign-color-background-darker: #3a3b3c; - --nldesign-color-background-hover: #252728; - --nldesign-color-border: #535353; - --nldesign-color-border-dark: #929292; - --nldesign-color-button-primary-background: #81afe2; - --nldesign-color-button-primary-border: #81afe2; - --nldesign-color-button-primary-hover: #6397da; + --nldesign-color-background-dark: #252525; + --nldesign-color-background-darker: #373737; + --nldesign-color-background-hover: #1d1d1d; + --nldesign-color-border: #252525; + --nldesign-color-border-dark: #878787; + --nldesign-color-button-primary-background: #4d6fb2; + --nldesign-color-button-primary-border: #4d6fb2; + --nldesign-color-button-primary-hover: #6b87bf; --nldesign-color-button-primary-text: #9e9e9e; --nldesign-color-error: #d83d32; --nldesign-color-error-hover: #e2726d; --nldesign-color-error-rgb: 216, 61, 50; - --nldesign-color-favorite: #f48c25; - --nldesign-color-focus: #39adf5; - --nldesign-color-focus-rgb: 57, 173, 245; - --nldesign-color-header-background: #141414; - --nldesign-color-header-text: #cccccc; - --nldesign-color-info: #39adf5; - --nldesign-color-info-rgb: 57, 173, 245; - --nldesign-color-link: #81afe2; - --nldesign-color-link-hover: #39adf5; - --nldesign-color-link-visited: #c392e3; + --nldesign-color-favorite: #f321a5; + --nldesign-color-focus: #ebebeb80; + --nldesign-color-focus-rgb: 235, 235, 235; + --nldesign-color-header-background: #4d6fb2; + --nldesign-color-header-text: #9e9e9e; + --nldesign-color-info: #4c8cc4; + --nldesign-color-info-rgb: 76, 140, 196; + --nldesign-color-link: #4d6fb2; + --nldesign-color-link-hover: #6b87bf; + --nldesign-color-link-visited: #4c8cc4; --nldesign-color-nav-background: #141414; - --nldesign-color-placeholder-dark: #3f3f3f; - --nldesign-color-placeholder-light: #2a2a2a; - --nldesign-color-primary: #2f80d9; - --nldesign-color-primary-hover: #1d5499; - --nldesign-color-primary-light: #112131; - --nldesign-color-primary-light-hover: #172d41; - --nldesign-color-primary-text: #111111; + --nldesign-color-placeholder-dark: #373737; + --nldesign-color-placeholder-light: #252525; + --nldesign-color-primary: #476db8; + --nldesign-color-primary-hover: #395793; + --nldesign-color-primary-light: #151f34; + --nldesign-color-primary-light-hover: #1e2d4b; + --nldesign-color-primary-text: #fefefe; --nldesign-color-success: #9ceb6f; --nldesign-color-success-rgb: 156, 235, 111; - --nldesign-color-text: #cccccc; + --nldesign-color-text: #ebebeb; --nldesign-color-text-light: #9e9e9e; --nldesign-color-text-muted: #9e9e9e; --nldesign-color-warning: #f48c25; --nldesign-color-warning-rgb: 244, 140, 37; - --nldesign-component-badge-background-color: #81afe2; + --nldesign-component-badge-background-color: #4d6fb2; --nldesign-component-badge-color: #9e9e9e; - --nldesign-component-button-active-background-color: #172d41; - --nldesign-component-button-active-color: #8cb9ea; - --nldesign-component-button-border-color: #81afe2; - --nldesign-component-button-color: #8cb9ea; + --nldesign-component-button-active-background-color: #1e2d4b; + --nldesign-component-button-active-color: #7390c9; + --nldesign-component-button-border-color: #4d6fb2; + --nldesign-component-button-color: #7390c9; --nldesign-component-button-disabled-background-color: #2a2a2a; --nldesign-component-button-disabled-border-color: #535353; --nldesign-component-button-disabled-color: #9e9e9e; - --nldesign-component-button-focus-border-color: #39adf5; - --nldesign-component-button-hover-background-color: #112131; - --nldesign-component-button-hover-border-color: #6397da; - --nldesign-component-button-hover-color: #8cb9ea; - --nldesign-component-button-primary-action-background-color: #81afe2; - --nldesign-component-button-primary-action-border-color: #81afe2; + --nldesign-component-button-focus-border-color: #ebebeb80; + --nldesign-component-button-hover-background-color: #151f34; + --nldesign-component-button-hover-border-color: #6b87bf; + --nldesign-component-button-hover-color: #7390c9; + --nldesign-component-button-primary-action-background-color: #4d6fb2; + --nldesign-component-button-primary-action-border-color: #4d6fb2; --nldesign-component-button-primary-action-color: #9e9e9e; - --nldesign-component-button-primary-action-hover-background-color: #6397da; + --nldesign-component-button-primary-action-hover-background-color: #6b87bf; --nldesign-component-button-primary-action-hover-color: #9e9e9e; - --nldesign-component-button-secondary-action-border-color: #81afe2; - --nldesign-component-button-secondary-action-color: #8cb9ea; - --nldesign-component-button-secondary-action-hover-background-color: #112131; - --nldesign-component-form-field-label-color: #cccccc; - --nldesign-component-form-fieldset-border-color: #535353; - --nldesign-component-form-select-border-color: #535353; - --nldesign-component-form-select-focus-border-color: #39adf5; - --nldesign-component-heading-1-color: #cccccc; - --nldesign-component-heading-2-color: #cccccc; - --nldesign-component-heading-3-color: #cccccc; - --nldesign-component-heading-4-color: #cccccc; - --nldesign-component-heading-5-color: #cccccc; - --nldesign-component-heading-6-color: #cccccc; - --nldesign-component-link-color: #8cb9ea; - --nldesign-component-link-hover-color: #3db5ff; - --nldesign-component-link-visited-color: #cea0eb; - --nldesign-component-ordered-list-color: #cccccc; - --nldesign-component-paragraph-color: #cccccc; - --nldesign-component-separator-border-color: #535353; - --nldesign-component-table-border-color: #535353; - --nldesign-component-table-header-background-color: #2c2e2f; - --nldesign-component-table-header-color: #cccccc; - --nldesign-component-table-row-hover-background-color: #252728; + --nldesign-component-button-secondary-action-border-color: #4d6fb2; + --nldesign-component-button-secondary-action-color: #7390c9; + --nldesign-component-button-secondary-action-hover-background-color: #151f34; + --nldesign-component-form-field-label-color: #ebebeb; + --nldesign-component-form-fieldset-border-color: #252525; + --nldesign-component-form-select-border-color: #252525; + --nldesign-component-form-select-focus-border-color: #4c8cc4; + --nldesign-component-heading-1-color: #ebebeb; + --nldesign-component-heading-2-color: #ebebeb; + --nldesign-component-heading-3-color: #ebebeb; + --nldesign-component-heading-4-color: #ebebeb; + --nldesign-component-heading-5-color: #ebebeb; + --nldesign-component-heading-6-color: #ebebeb; + --nldesign-component-heading-color: #ebebeb; + --nldesign-component-link-color: #7390c9; + --nldesign-component-link-hover-color: #7390c9; + --nldesign-component-link-visited-color: #68a2d4; + --nldesign-component-ordered-list-color: #ebebeb; + --nldesign-component-paragraph-color: #ebebeb; + --nldesign-component-separator-border-color: #252525; + --nldesign-component-table-border-color: #252525; + --nldesign-component-table-header-background-color: #252525; + --nldesign-component-table-header-color: #ebebeb; + --nldesign-component-table-row-hover-background-color: #1d1d1d; --nldesign-component-textbox-background-color: #9e9e9e; --nldesign-component-textbox-border-color: #9e9e9e; - --nldesign-component-textbox-color: #cccccc; + --nldesign-component-textbox-color: #ebebeb; --nldesign-component-textbox-disabled-background-color: #9e9e9e; --nldesign-component-textbox-disabled-color: #9e9e9e; - --nldesign-component-textbox-focus-border-color: #3db5ff; + --nldesign-component-textbox-focus-border-color: #68a2d4; --nldesign-component-textbox-hover-border-color: #9e9e9e; --nldesign-component-textbox-invalid-border-color: #e75f55; - --nldesign-component-unordered-list-color: #cccccc; + --nldesign-component-unordered-list-color: #ebebeb; + --zwolle-color-black: #ebebeb; + --zwolle-color-blue-40: #6b87bf; + --zwolle-color-blue-45: #4c8cc4; + --zwolle-color-blue-50: #4d6fb2; + --zwolle-color-grey: #252525; + --zwolle-color-pink-50: #f321a5; + --zwolle-color-white: #141414; } diff --git a/docs/features/token-editor.md b/docs/features/token-editor.md index 0f3fb555..5d4b765a 100644 --- a/docs/features/token-editor.md +++ b/docs/features/token-editor.md @@ -21,6 +21,8 @@ The token editor is located in the **NL Design System Theme** section of the App Tokens are grouped into 4 tabs. Click a tab to switch between categories. The active tab is highlighted. +The tokens listed below are Nextcloud's own base tokens. They are locked until you tick **Also edit Nextcloud's base tokens** above the tabs and confirm the warning: each one is read by far more of Nextcloud than any single component, so changing it changes much more than the part you are looking at. To change one component, use that component's own rows in the preview instead. The base tokens lock again when you untick the box or leave the page. + ### Login Page & Branding (12 tokens) Controls the primary brand colors used throughout the interface — buttons, links, highlights, and the login page header. @@ -83,7 +85,7 @@ Each token row contains: - **CSS variable** — the actual CSS custom property name (e.g., `--color-primary`) - **Color picker** — click to open a native color picker (color tokens only) - **Hex input** — type a hex value directly (e.g., `#c00000`) -- **Reset button** (↺) — reverts the token to the value from the current token set +- **Reset button** (↺) — undoes unsaved edits: puts back the value you last saved, or the token set's value if you never saved one The color picker and hex input are always in sync — changing one updates the other instantly. @@ -95,13 +97,29 @@ When a token has been manually overridden (saved to `custom-overrides.css`), a s The badge color matches the overridden value, making it easy to identify which tokens have been customized at a glance. -Clicking the reset button (↺) removes the override and the badge disappears — the token returns to the token set's default value. +To stop overriding a token, empty its field and click **Save overrides** — the token returns to the token set's value. The reset button (↺) only undoes edits you have not saved yet. ## Live Preview -Every change you make is applied immediately as a CSS variable override on the current page — you can see the effect instantly in the **Preview** section above the editor (shows sample primary and secondary buttons). +Every change you make is shown immediately in the **Preview** section above the editor. It is applied to the preview only: the rest of Nextcloud keeps the saved theme until you save. + +Saving applies it — with **Save overrides** on the active theme, or by saving the changes as a new theme and selecting it. Reloading the page without saving reverts all changes. + +### Nextcloud version + +Components that Nextcloud 32 to 35 draw differently have a **Nextcloud version** selector in the preview. It starts on the version your instance runs, and each component keeps its own choice. Components that every version draws the same have no selector. Under the preview it lists what the chosen version does differently for that component. The header and the note cards are also drawn as the chosen version, and the note cards show the rows that only that version reads. Other components keep the look of the version you run, and the preview says so while another version is chosen. + +### Header app icons on Nextcloud 32 and 33 + +On Nextcloud 32 and 33 the app menu in the header (Dashboard, Files, Photos, …) is drawn with white icon images. **Header glyphs** colours the icons that are drawn as glyphs — search, notifications, contacts and the account menu — but it cannot recolour images, so the app icons stay white whatever the header background is. Keep that in mind when you give the header a light background on these versions. + +From Nextcloud 34 the app menu is a glyph as well, and follows **Header glyphs**. + +### Toast and note card text + +Toasts and note cards have a background and a text colour per type (success, error, warning, info). When a theme sets a background but no text colour, the text is chosen automatically: white on a dark background, black on a light one. When the theme sets neither, toasts and note cards look exactly as Nextcloud draws them. -The live preview is not saved until you click **Save overrides**. Reloading the page without saving reverts all changes. +From Nextcloud 33 the note card colour is the card's fill. On Nextcloud 32 it is the stripe on the left, and the fill is a light tint mixed from a separate **fill (Nextcloud 32)** colour. Select **32** above the note card preview to see and set those; they are hidden for 33 and later, where Nextcloud does not read them. The automatic text colour compares against the card colour, so on Nextcloud 32 pick the text colour yourself if the automatic one does not suit the light fill. ## Saving Overrides diff --git a/js/admin.js b/js/admin.js index 8d0b21cd..a18c6d9b 100644 --- a/js/admin.js +++ b/js/admin.js @@ -95,8 +95,79 @@ var settingsEl = document.getElementById('nldesign-settings') var tokenSetSelect = document.getElementById('nldesign-token-set-select') var hideSloganCheckbox = document.getElementById('nldesign-hide-slogan') + var primaryDrivesComponentsCheckbox = document.getElementById( + 'nldesign-primary-drives-components', + ) var previewRoot = document.getElementById('nldesign-preview') + // Whether the brand primary currently overrules the component tokens it + // used to drive. Read off the server-rendered checkbox rather than through + // initial state: the template already carries the value, and the flag has + // to stay in step with the control the admin is looking at. + var primaryDrivesComponents = + primaryDrivesComponentsCheckbox !== null + && primaryDrivesComponentsCheckbox.checked === true + + /** + * Whether a token's control is locked by the "primary drives every + * component" setting. + * + * Only component tokens carry `primary`. The brand tokens never lock — + * that setting exists to make THEM win, so freezing them would leave the + * admin with no way to change anything at all. + * + * @param {string} name The CSS custom property name. + * + * @return {boolean} True when the row must render disabled. + */ + function isTokenLocked(name) { + var meta = tokenRegistry[name] + if (meta === undefined) { + return false + } + if (meta.group === 'brand' && baseTokensUnlocked === false) { + return true + } + return primaryDrivesComponents === true && meta.primary === true + } + + /** + * Whether Nextcloud's own base tokens can be edited. + * + * Off until the admin asks, and only for this visit. A base token — + * `--color-main-text`, `--color-primary-element` — is read by far more of + * Nextcloud than any one component, so changing one repaints things an + * admin did not set out to change. The component rows are the everyday + * control; these stay behind a deliberate, warned opt-in. + * + * @type {boolean} + */ + var baseTokensUnlocked = false + + /** + * Why a locked row is locked, for its tooltip. + * + * @param {string} name The CSS custom property name. + * + * @return {string} The reason, or '' when the row is not locked. + */ + function lockReason(name) { + if (isTokenLocked(name) === false) { + return '' + } + var meta = tokenRegistry[name] + if (meta !== undefined && meta.group === 'brand') { + return t( + 'thematiq', + 'A Nextcloud base colour, read far beyond any one component. Tick "Also edit Nextcloud\'s base tokens" to change it.', + ) + } + return t( + 'thematiq', + 'The primary colour drives this component. Switch off "Let the primary colour drive every component" to set it separately.', + ) + } + // Read a live CSS custom property with a fallback. Reads from first // (Nextcloud and the nldesign themes set their token vars there), then :root. function readVar(name, fallback) { @@ -228,6 +299,7 @@ pageTokenSetId = tokenSetId pageManifest = manifests[1] + syncOverridesLink(tokenSetId) return true }, ) @@ -235,6 +307,45 @@ ) } + /** + * The set whose overrides the editor reads and writes. + * + * The stock set keeps its edits in a file of its own and never loads + * the shared one (CustomOverridesService), so every overrides request + * names the set it means rather than leaving the server to assume the + * instance's active one. Read like onStockSet(): the dropdown first, + * because it can move under the editor without a reload. + * + * @return {string} The token set id, or '' when unknown. + */ + function editedTokenSetId() { + if (tokenSetSelect !== null && tokenSetSelect.value !== '') { + return tokenSetSelect.value + } + + return pageTokenSetId + } + + /** + * An overrides endpoint URL for one token set. + * + * @param {string} path The path after /settings/overrides ('' or '/export'). + * @param {string} tokenSet The token set id. + * + * @return {string} The URL. + */ + function overridesUrl(path, tokenSet) { + return ( + OC.generateUrl('/apps/thematiq/settings/overrides' + path) + + '?tokenSet=' + + encodeURIComponent(tokenSet) + ) + } + + // Both overrides files: the shared one and the stock set's own. + var OVERRIDES_LINK_SELECTOR = + 'link[rel="stylesheet"][href*="/thematiq/css/custom-overrides"]' + /** * custom-overrides.css changed under the same URL (the apply dialog or * the token editor wrote it): make the browser fetch it again. @@ -243,10 +354,52 @@ if (LayerSwap === null) { return } - LayerSwap.refreshStylesheets( - document, - 'link[rel="stylesheet"][href*="/thematiq/css/custom-overrides.css"]', - ) + LayerSwap.refreshStylesheets(document, OVERRIDES_LINK_SELECTOR) + } + + /** + * Point the overrides link at the file the given set reads. + * + * The server emits that link after the set's own run, so a swap without + * a reload leaves it on the OLD set's file: a switch to the stock set + * would keep painting the edits of the theme just left. + * + * @param {string} tokenSetId The set now on the page. + * + * @return {void} + */ + function syncOverridesLink(tokenSetId) { + var link = document.querySelector(OVERRIDES_LINK_SELECTOR) + if (link === null) { + return + } + + // The same rule as CustomOverridesService::fileFor(): a set on no + // design system has a file of its own, every other set the shared one. + var option = + tokenSetSelect !== null + ? tokenSetSelect.querySelector( + 'option[value="' + tokenSetId + '"]', + ) + : null + var designSystem = + option !== null + ? option.getAttribute('data-design-system') || 'nldesign' + : 'nldesign' + var file = + designSystem === 'none' || tokenSetId === STOCK_TOKEN_SET + ? 'custom-overrides-' + + tokenSetId.toLowerCase().replace(/[^a-z0-9-]/g, '') + : 'custom-overrides' + var href = OC.filePath('thematiq', 'css', file + '.css') + if ( + LayerSwap.pathnameOf(link.getAttribute('href')) + === LayerSwap.pathnameOf(href) + ) { + return + } + + link.setAttribute('href', href + '?v=' + Date.now()) } /** @@ -619,81 +772,100 @@ // the live NC token values. Primary comes from the selected set's metadata // (it isn't applied until confirmed); the remaining colours mirror the live // theme, so the token-editor pickers reflect into the preview too. + /** + * Repaint the rich preview from the live CSS stack. + * + * READ EVERYTHING, THEN WRITE EVERYTHING — and that split is the whole + * performance of this panel. + * + * This used to interleave the two: read a variable, set a `--prev-*`, + * read the next. Writing to `previewRoot.style` invalidates style for + * the subtree, so the read after it could not be served from the last + * computation and forced a fresh synchronous one. Eighteen properties + * meant eighteen full style recalculations, against a cascade that + * carries theme.css, element-overrides.css, component-scopes.css and + * the vendored guest sheet. The colour picker fires `input` on every + * pixel of a drag, so that ran dozens of times a second — which is what + * made the picker stutter. + * + * Reading first means one recalculation for the batch, and the writes + * afterwards cost nothing until the next frame paints. + * + * @param {string} tokenSet The token set id the preview is drawn for. + * + * @return {void} + */ function updatePreview(tokenSet) { if (!previewRoot) { return } + var colors = getPreviewColors(tokenSet) - var s = previewRoot.style - s.setProperty('--prev-primary', colors.primary) - s.setProperty( - '--prev-primary-text', - colors.primaryText || readVar('--color-primary-text', '#ffffff'), - ) - s.setProperty( - '--prev-surface', - readVar('--color-main-background', '#ffffff'), - ) - s.setProperty('--prev-bg', readVar('--color-background-dark', '#f2f4f7')) - s.setProperty('--prev-text', readVar('--color-main-text', '#1b2733')) - s.setProperty( - '--prev-muted', - readVar('--color-text-maxcontrast', '#6b7785'), - ) - s.setProperty('--prev-border', readVar('--color-border', '#e3e9f0')) - s.setProperty('--prev-warning', readVar('--color-warning', '#c79a00')) - s.setProperty('--prev-error', readVar('--color-error', '#c0392b')) - s.setProperty( - '--prev-info', - readVar( + + // ---- read phase ------------------------------------------------- + // Both declarations are resolved once here instead of per lookup; + // `readVar` builds a new pair on every call. + // Read on the preview itself: an unsaved edit is declared there, + // and everything else reaches it by inheritance. + var previewStyle = getComputedStyle(previewRoot) + + function read(name, fallback) { + return (previewStyle.getPropertyValue(name) || '').trim() || fallback + } + + var primaryText = + colors.primaryText || read('--color-primary-text', '#ffffff') + + var values = { + '--prev-primary': colors.primary, + '--prev-primary-text': primaryText, + '--prev-surface': read('--color-main-background', '#ffffff'), + '--prev-bg': read('--color-background-dark', '#f2f4f7'), + '--prev-text': read('--color-main-text', '#1b2733'), + '--prev-muted': read('--color-text-maxcontrast', '#6b7785'), + '--prev-border': read('--color-border', '#e3e9f0'), + '--prev-warning': read('--color-warning', '#c79a00'), + '--prev-error': read('--color-error', '#c0392b'), + '--prev-info': read( '--color-info', - readVar('--color-primary-element', colors.primary), + read('--color-primary-element', colors.primary), ), - ) - s.setProperty('--prev-radius', readVar('--border-radius-element', '8px')) - // The rounded container Nextcloud clips the navigation and the app - // content into, and the pill radius of a navigation entry. - s.setProperty( - '--prev-radius-container', - readVar( + '--prev-radius': read('--border-radius-element', '8px'), + // The rounded container Nextcloud clips the navigation and the + // app content into, and the pill radius of a navigation entry. + '--prev-radius-container': read( '--body-container-radius', - readVar('--border-radius-large', '12px'), + read('--border-radius-large', '12px'), ), - ) - s.setProperty( - '--prev-radius-pill', - readVar('--border-radius-pill', '999px'), - ) - // The page background BEHIND the content container — visible in the - // gap around it, which is where a themed instance shows its plain - // colour or background image. - s.setProperty( - '--prev-plain', - readVar( + '--prev-radius-pill': read('--border-radius-pill', '999px'), + // The page background BEHIND the content container — visible in + // the gap around it, which is where a themed instance shows its + // plain colour or background image. + '--prev-plain': read( '--color-background-plain', - readVar('--color-background-dark', '#f2f4f7'), + read('--color-background-dark', '#f2f4f7'), ), - ) - // The header is its own role: a set may paint it differently from the - // primary (Cunningham and Amsterdam both paint it white, over a blue - // primary). Fall back to the primary only when the page carries no - // header token. - s.setProperty( - '--prev-header-bg', - readVar('--nldesign-color-header-background', colors.primary), - ) - s.setProperty( - '--prev-header-text', - readVar( + // The header is its own role: a set may paint it differently + // from the primary (Cunningham and Amsterdam both paint it white + // over a blue primary). Fall back to the primary only when the + // page carries no header token. + '--prev-header-bg': read( + '--nldesign-color-header-background', + colors.primary, + ), + '--prev-header-text': read( '--nldesign-color-header-text', - colors.primaryText || readVar('--color-primary-text', '#ffffff'), + primaryText, ), - ) - s.setProperty( - '--prev-header-border', - readVar('--nldesign-header-border-bottom', '0'), - ) - s.setProperty('--prev-login-bg', colors.primary) + '--prev-header-border': read('--nldesign-header-border-bottom', '0'), + '--prev-login-bg': colors.primary, + } + + // ---- write phase ------------------------------------------------ + var s = previewRoot.style + Object.keys(values).forEach(function (name) { + s.setProperty(name, values[name]) + }) } // App / Login preview switch. @@ -1175,7 +1347,16 @@ var defaultLabel = t('thematiq', 'Nextcloud default') - if ((tokenSetData.design_system || 'nldesign') === 'none') { + // A stock-based theme resets Nextcloud's branding to its defaults — + // unless it CAPTURED the branding it was saved with, in which case + // that is what it brings back, like any other theme. + var captured = Boolean( + tokenSetData.theming && tokenSetData.theming.captured === true, + ) + if ( + (tokenSetData.design_system || 'nldesign') === 'none' + && captured === false + ) { var resetDiffs = [] if (currentTheming.primary_color) { resetDiffs.push({ @@ -1286,6 +1467,33 @@ payload.logo = proposed.logo } + // The navigation-bar logo and the favicon, compared the same way as + // the logo: by the file Thematiq last put in the slot. + ;[ + ['logoheader', t('thematiq', 'Navigation bar logo')], + ['favicon', t('thematiq', 'Favicon')], + ].forEach(function (slot) { + var key = slot[0] + if ( + proposed[key] + && !( + currentTheming['has_custom_' + key] === true + && currentTheming['synced_' + key] === proposed[key] + ) + ) { + diffs.push({ + label: slot[1], + key: key, + kind: 'text', + current: currentTheming['has_custom_' + key] + ? t('thematiq', '(custom)') + : t('thematiq', '(default)'), + proposed: proposed[key].split('/').pop(), + }) + payload[key] = proposed[key] + } + }) + if ( proposed.background && !( @@ -1305,9 +1513,49 @@ payload.background = proposed.background } + // A captured theme also knows whether the background image had been + // removed ("color") or left as Nextcloud's own ("default"). Offered + // only when the page is in a different state; an image of the + // theme's own is the background row above. + var mode = proposed.background_mode + var removedNow = currentTheming.background_mime === 'backgroundColor' + var currentBackground = removedNow + ? t('thematiq', 'Removed (plain colour)') + : currentTheming.has_custom_background + ? t('thematiq', '(custom)') + : t('thematiq', '(default)') + if (mode === 'color' && removedNow === false) { + diffs.push({ + label: t('thematiq', 'Background image'), + key: 'background_mode', + kind: 'text', + current: currentBackground, + proposed: t('thematiq', 'Removed (plain colour)'), + }) + } else if ( + mode === 'default' + && (removedNow === true + || currentTheming.has_custom_background === true) + ) { + diffs.push({ + label: t('thematiq', 'Background image'), + key: 'background_mode', + kind: 'text', + current: currentBackground, + proposed: t('thematiq', 'Nextcloud default'), + }) + } + if (diffs.length === 0) { return none } + + // Sent with anything that is applied, so a colour change on its own + // cannot drop the background image the theme was saved with. + if (mode === 'image' || mode === 'color' || mode === 'default') { + payload.background_mode = mode + } + return { mode: 'match', diffs: diffs, payload: payload } } @@ -1817,11 +2065,25 @@ payload[diff.key] = diff.proposed } else if ( diff.key === 'logo' + || diff.key === 'logoheader' + || diff.key === 'favicon' || diff.key === 'background' ) { payload[diff.key] = proposed[diff.key] } }) + // A theme that captured Nextcloud's branding says which + // background state it was saved in; see computeThemingPlan(). + if ( + Object.keys(payload).length > 0 + || diffs.some(function (diff) { + return diff.key === 'background_mode' + }) + ) { + if (proposed.background_mode) { + payload.background_mode = proposed.background_mode + } + } var url = OC.generateUrl('/apps/thematiq/settings/theming') fetch(url, { @@ -2012,6 +2274,13 @@ }) } + // Handle the "primary drives every component" checkbox + if (primaryDrivesComponentsCheckbox) { + primaryDrivesComponentsCheckbox.addEventListener('change', function () { + savePrimaryDrivesComponentsSetting(this.checked) + }) + } + // Handle show menu labels checkbox var showMenuLabelsCheckbox = document.getElementById( 'nldesign-show-menu-labels', @@ -2157,6 +2426,82 @@ }) } + // Save the "primary drives every component" setting to the server, then + // bring this page into the state it just asked for: add or drop + // primary-lock.css so the specimens and the playground repaint, and + // re-render the editor so the rows the primary now owns lock or unlock. + function savePrimaryDrivesComponentsSetting(enabled) { + var url = OC.generateUrl( + '/apps/thematiq/settings/primary-drives-components', + ) + + fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + requesttoken: OC.requestToken, + }, + body: JSON.stringify({ primaryDrivesComponents: enabled }), + }) + .then(function (response) { + return response.json() + }) + .then(function (data) { + if (data.status === 'ok') { + primaryDrivesComponents = enabled === true + setConditionalLayer('primary-lock', primaryDrivesComponents) + refreshTokenEditorLocks() + notify(t('thematiq', 'Applied.')) + } else { + notify(t('thematiq', 'Failed to save setting.')) + } + }) + .catch(function (error) { + console.error( + 'Error saving primary-drives-components setting:', + error, + ) + notify(t('thematiq', 'Failed to save setting.')) + }) + } + + /** + * Lock or unlock the editor rows the primary owns, in place. + * + * Deliberately not a re-render: the editor holds unsaved edits in + * `tokenEditorState`, and rebuilding the panels would drop them and reset + * the open tab. Only the two attributes that express the lock are touched. + * + * @return {void} + */ + function refreshTokenEditorLocks() { + var container = document.getElementById('nldesign-token-editor') + if (container === null) { + return + } + + container + .querySelectorAll('.nldesign-token-row') + .forEach(function (row) { + var locked = isTokenLocked(row.dataset.tokenRow) + var reason = lockReason(row.dataset.tokenRow) + row.classList.toggle('nldesign-token-row--locked', locked) + row.querySelectorAll('input').forEach(function (input) { + input.disabled = locked + if (reason === '') { + input.removeAttribute('title') + } else { + input.setAttribute('title', reason) + } + }) + // The reset button too: resetting a locked row changes it. + var reset = row.querySelector('.nldesign-reset-btn') + if (reset !== null) { + reset.disabled = locked + } + }) + } + // Save show menu labels setting to server. function saveMenuLabelsSetting(showMenuLabels) { var url = OC.generateUrl('/apps/thematiq/settings/menulabels') @@ -2200,6 +2545,61 @@ // Tab labels from server: tab id → display label var tokenTabLabels = {} + /** + * The value a token row should show. + * + * A component token is deliberately UNDECLARED until somebody sets one: + * that is what makes `var(--nldesign-component-x, var(--global))` in + * css/component-scopes.css fall through to the brand value, and what + * makes an untouched instance render exactly as it did before the + * component layer existed. The cost is that `getPropertyValue` reports + * it as the empty string, so every one of those rows opened blank — + * a white swatch beside a component that is plainly not white. + * + * So when the token itself is unset, the row shows the Nextcloud + * variable it falls back to: the colour the component is ACTUALLY + * wearing. The registry carries that name as `global`. + * + * This is display only. `saveOverrides()` writes a token only when its + * value differs from `resolved`, and `resolved` is what this returned — + * so showing the inherited colour never writes it, and the fallback + * stays live until the admin actually picks something. + * + * The FALLBACK is read off `` first, for the same reason + * css/component-scopes.css captures it there: the design systems declare + * the Nextcloud globals on `body` and Nextcloud core declares its own on + * `:root`. Reading the global off the document element returned CORE's + * colour, so a row for a token nobody had set opened showing Nextcloud + * blue beside a component wearing the brand colour. + * + * The token's OWN value is still read off `:root` — that is where + * defaults.css and custom-overrides.css declare `--nldesign-*`. + * + * @param {CSSStyleDeclaration} rootStyle Computed style of the document element. + * @param {CSSStyleDeclaration} bodyStyle Computed style of the body element. + * @param {string} name The CSS custom property name. + * + * @return {string} The resolved value, or its fallback, or ''. + */ + function resolveTokenValue(rootStyle, bodyStyle, name) { + var own = rootStyle.getPropertyValue(name).trim() + if (own !== '') { + return own + } + + var meta = tokenRegistry[name] + if (meta === undefined || !meta.global) { + return '' + } + + var inherited = bodyStyle.getPropertyValue(meta.global).trim() + if (inherited !== '') { + return inherited + } + + return rootStyle.getPropertyValue(meta.global).trim() + } + /** * Initialise and mount the token editor panel into #nldesign-token-editor. */ @@ -2209,7 +2609,7 @@ return } - fetch(OC.generateUrl('/apps/thematiq/settings/overrides'), { + fetch(overridesUrl('', editedTokenSetId()), { headers: { requesttoken: OC.requestToken }, }) .then(function (r) { @@ -2222,14 +2622,18 @@ // Read resolved values from the live CSS stack. var rootStyle = getComputedStyle(document.documentElement) + var bodyStyle = getComputedStyle(document.body) Object.keys(tokenRegistry).forEach(function (name) { - var resolved = rootStyle.getPropertyValue(name).trim() + var resolved = resolveTokenValue(rootStyle, bodyStyle, name) var overridden = overrides[name] !== undefined ? overrides[name] : null tokenEditorState[name] = { resolved: resolved, custom: overridden, current: overridden !== null ? overridden : resolved, + // What the reset button goes back to: the value as it + // was last saved, not the theme's own. + saved: overridden !== null ? overridden : resolved, isDirty: false, } }) @@ -2312,6 +2716,14 @@ + '' + '' + '' + // Nextcloud's own base tokens stay locked until asked for; see + // baseTokensUnlocked and confirmBaseUnlock(). + + '' + '
' + tabsHtml + '
' @@ -2323,6 +2735,31 @@ + '' + '' + '' + // The way back. "Do not ask again" lives inside a dialog the + // admin has just dismissed, so without these two the offer to + // keep a set of changes as its own theme would be gone for good + // after one careless click. Worded the same way round as that + // dialog: ticked means "stop asking". + + '
' + + '' + + '' + + '
' + '' container.querySelectorAll('.nldesign-tab-btn').forEach(function (btn) { @@ -2350,6 +2787,10 @@ wireTokenRows(container) + wireConfirmControls() + + wireBaseUnlock() + document .getElementById('nldesign-save-btn') .addEventListener('click', saveOverrides) @@ -2368,6 +2809,88 @@ }) } + /** + * Wire the "also edit Nextcloud's base tokens" toggle. + * + * Unticking locks the base rows again straight away. Ticking asks + * first, and the box only stays ticked when the admin confirms. + * + * @return {void} + */ + function wireBaseUnlock() { + var box = document.getElementById('nldesign-base-unlock') + if (box === null) { + return + } + + box.addEventListener('change', function () { + if (box.checked === false) { + baseTokensUnlocked = false + refreshTokenEditorLocks() + return + } + box.checked = false + confirmBaseUnlock(function () { + box.checked = true + baseTokensUnlocked = true + refreshTokenEditorLocks() + }) + }) + } + + /** + * Warn before the base tokens are unlocked. + * + * @param {Function} proceed Called when the admin confirms. + * + * @return {void} + */ + function confirmBaseUnlock(proceed) { + var html = + '
' + + '
' + + '

' + + escapeHtml(t('thematiq', "Edit Nextcloud's base tokens?")) + + '

' + + '

' + + escapeHtml( + t( + 'thematiq', + 'These are the colours and sizes Nextcloud itself is built from, such as the main text colour. Changing one changes every part of Nextcloud that reads it, which is far more than the component you are looking at, and not only what this panel shows. To change one component, use its own rows instead.', + ), + ) + + '

' + + '
' + + '' + + '' + + '
' + + '
' + + '
' + + document.body.insertAdjacentHTML('beforeend', html) + var overlay = document.getElementById('nldesign-base-unlock-overlay') + + function close() { + overlay.remove() + } + + makeDialogAccessible(overlay, close) + + overlay + .querySelector('.nldesign-dialog-cancel') + .addEventListener('click', close) + overlay + .querySelector('.nldesign-dialog-confirm') + .addEventListener('click', function () { + close() + proceed() + }) + } + /** * Build HTML for a single token row. */ @@ -2397,25 +2920,46 @@ }), ) + // Locked rows render disabled rather than hidden. The value is still + // stored and still shown, because turning the setting back off restores + // it — hiding the control would make a kept value look like a lost one. + // `disabled` also carries to the playground, which clones these rows. + var locked = isTokenLocked(name) + var lockedAttr = locked + ? ' disabled title="' + escapeHtml(lockReason(name)) + '"' + : '' + var inputHtml = '' - if (meta.type === 'color') { + // An `rgb` token holds a bare `r, g, b` triplet (Nextcloud 32 mixes + // its note-card fills from one), so it gets the same picker as a + // colour and the picker writes the triplet — see wireTokenRows(). + var format = meta.type === 'rgb' ? ' data-format="rgb"' : '' + if (meta.type === 'color' || meta.type === 'rgb') { var pickerVal = normaliseColorForPicker(displayVal) inputHtml = '
' + '' + + '"' + + lockedAttr + + '>' + '' + + '"' + + lockedAttr + + '>' + '
' } else { inputHtml = @@ -2425,11 +2969,15 @@ + escapeHtml(name) + '" value="' + escapeHtml(displayVal) - + '">' + + '"' + + lockedAttr + + '>' } return ( - '
' + '
' @@ -2452,11 +3000,53 @@ label: meta.label || name, }), ) - + '">↺' + + '"' + + (locked ? ' disabled' : '') + + '>↺' + '
' ) } + /** + * The value a colour picker stands for. + * + * A picker always reports #RRGGBB; on an `rgb` row the token holds the + * bare triplet instead, so the hex is converted before it is written. + * + * @param {HTMLInputElement} picker The colour input. + * @return {string} The token value. + */ + function pickerValue(picker) { + if ( + picker.dataset.format === 'rgb' + && typeof TT.hexToRgbTriplet === 'function' + ) { + return TT.hexToRgbTriplet(picker.value) || picker.value + } + return picker.value + } + + /** + * Move a picker to a typed value it can show: a hex colour, or on an + * `rgb` row a triplet. Anything else leaves the swatch where it is. + * + * @param {HTMLInputElement} picker The colour input. + * @param {string} value The typed value. + * @return {void} + */ + function syncPicker(picker, value) { + if (/^#[0-9a-fA-F]{6}$/.test(value) === true) { + picker.value = value + return + } + if ( + picker.dataset.format === 'rgb' + && /^\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}$/.test(value) === true + ) { + picker.value = normaliseColorForPicker(value) + } + } + /** * Wire event listeners on all token rows inside a container. */ @@ -2466,7 +3056,7 @@ .forEach(function (picker) { picker.addEventListener('input', function () { var name = picker.dataset.token - var value = picker.value + var value = pickerValue(picker) var textField = container.querySelector( '.nldesign-color-text[data-token="' + name + '"]', ) @@ -2487,11 +3077,8 @@ var picker = container.querySelector( '.nldesign-color-picker[data-token="' + name + '"]', ) - if ( - picker !== null - && /^#[0-9a-fA-F]{6}$/.test(value) === true - ) { - picker.value = value + if (picker !== null) { + syncPicker(picker, value) } applyLivePreview(name, value) markDirty(name, value, container) @@ -2518,7 +3105,10 @@ if (state === undefined) { return } - var defaultVal = state.resolved + // Back to the saved value, so a reset undoes the edit + // rather than a save the admin already made. + var defaultVal = + state.saved !== undefined ? state.saved : state.resolved var textField = container.querySelector( '.nldesign-color-text[data-token="' + name @@ -2546,23 +3136,92 @@ badge.remove() } } - document.documentElement.style.removeProperty(name) + // A saved value stays on the preview; with none, the + // preview falls back to what the theme itself declares. + if ( + typeof state.custom === 'string' + && state.custom !== '' + ) { + previewTarget().style.setProperty( + name, + state.custom, + 'important', + ) + } else { + previewTarget().style.removeProperty(name) + } + schedulePreviewRepaint() tokenEditorState[name].current = defaultVal - tokenEditorState[name].custom = null - tokenEditorState[name].isDirty = true + tokenEditorState[name].isDirty = false updateSaveStatus() }) }) } + // Pending preview repaint, so a drag cannot queue more than one a frame. + var previewFrame = null + + /** + * Ask for a preview repaint at the next frame, at most one per frame. + * + * A colour input fires `input` continuously while the pointer is down — + * well above 60 a second on a fast mouse — and each repaint reads the + * computed cascade. Repainting per event did work the frame could never + * show, because several events land between two paints and only the + * last one is visible. Coalescing keeps the preview exactly as current + * while doing a fraction of the work. + * + * @return {void} + */ + function schedulePreviewRepaint() { + if (previewFrame !== null) { + return + } + previewFrame = requestAnimationFrame(function () { + previewFrame = null + updatePreview(tokenSetSelect ? tokenSetSelect.value : '') + }) + } + + /** + * Where an unsaved edit is written: the preview, never the page. + * + * An edit is a preview until it is saved — as overrides on the active + * theme, or as a theme of its own that is then selected. Written on + * , every edit repainted the real Nextcloud the admin was working + * in, so the page wore a theme nobody had saved. On the preview it + * reaches exactly the specimens drawn inside it, which read the same + * tokens the real components do (css/component-scopes.css captures the + * Nextcloud values on the preview as well as on body). + * + * @return {Element} The preview root, or on a page without one. + */ + function previewTarget() { + return previewRoot !== null ? previewRoot : document.documentElement + } + function applyLivePreview(name, value) { + // The custom property is written immediately, NOT deferred: this is + // what repaints the specimens in the preview, and the browser + // already batches it into the next frame for free. Only the rich + // preview — which has to READ the cascade back — is worth delaying. + // + // WRITTEN `!important`, and it has to be. CustomOverridesService + // emits every stored token as `!important` (see the note in its + // write()), so once a token had been saved ONCE, this inline + // declaration lost the cascade to custom-overrides.css and dragging + // its picker moved nothing on the page. An admin who had already + // saved a colour could never preview a different one — which reads + // as the editor being dead, not as a cascade nicety. + // + // Inline `!important` outranks any author stylesheet, so the preview + // now wins over the stored value it is about to replace. if (value.trim() === '') { - document.documentElement.style.removeProperty(name) + previewTarget().style.removeProperty(name) } else { - document.documentElement.style.setProperty(name, value) + previewTarget().style.setProperty(name, value, 'important') } - // Reflect the live token edit into the rich preview. - updatePreview(tokenSetSelect ? tokenSetSelect.value : '') + schedulePreviewRepaint() } function markDirty(name, value, container) { @@ -2604,7 +3263,664 @@ dirtyCount > 0 ? t('thematiq', 'Unsaved changes') : '' } - function saveOverrides() { + /* ========================================================================== + * SAVE CONFIRMATION + * + * Two dialogs, because the two situations ask different questions. + * + * On the stock `nextcloud` set there is a real fork: the edits can be + * kept as a NEW token set, or written straight over the running + * Nextcloud theme. That choice has to be made before anything is + * written, because the two go to different places. + * + * On any other set there is nothing to choose — the overrides belong to + * the set that is on — so it is a plain confirmation. + * + * Both carry "do not ask again", and both flags are turned back on from + * the pair of controls under the editor. A one-way switch buried in a + * dialog is how an admin loses the only offer of the new-set path. + * ========================================================================== */ + + // The stock set's id, as CssInjectionService::STOCK_TOKEN_SET spells it. + // It is the one set whose "save" has a second route, because it is the + // running Nextcloud theme rather than a theme this app ships. + var STOCK_TOKEN_SET = 'nextcloud' + + // Server-rendered, and kept in step as the dialogs and the controls + // under the editor change them. + var confirmSaveStock = loadInitialState('confirmSaveStock', true) === true + var confirmSaveTheme = loadInitialState('confirmSaveTheme', true) === true + + /** + * Put the two controls under the editor in step with the flags. + * + * Called after a dialog changes one, so ticking "do not ask again" and + * then looking down at the controls shows the same fact in both places. + * + * @return {void} + */ + function refreshConfirmControls() { + var stockEl = document.getElementById('nldesign-confirm-save-stock') + var themeEl = document.getElementById('nldesign-confirm-save-theme') + // Ticked is "do not ask", the inverse of the stored flag. + if (stockEl !== null) { + stockEl.checked = confirmSaveStock === false + } + if (themeEl !== null) { + themeEl.checked = confirmSaveTheme === false + } + } + + /** + * Wire the two controls under the editor, and set their initial state. + * + * @return {void} + */ + function wireConfirmControls() { + var stockEl = document.getElementById('nldesign-confirm-save-stock') + var themeEl = document.getElementById('nldesign-confirm-save-theme') + if (stockEl === null || themeEl === null) { + return + } + + refreshConfirmControls() + + stockEl.addEventListener('change', function () { + confirmSaveStock = stockEl.checked === false + saveConfirmFlags() + }) + themeEl.addEventListener('change', function () { + confirmSaveTheme = themeEl.checked === false + saveConfirmFlags() + }) + } + + /** + * Persist both confirmation flags. + * + * Sent as a pair rather than one at a time: the controls under the + * editor render both, and a single round trip cannot leave the two + * halves of the preference disagreeing if one request fails. + * + * @return {Promise} Resolves when the server has stored them. + */ + function saveConfirmFlags() { + return fetch( + OC.generateUrl('/apps/thematiq/settings/save-confirmations'), + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + requesttoken: OC.requestToken, + }, + body: JSON.stringify({ + confirmSaveStock: confirmSaveStock, + confirmSaveTheme: confirmSaveTheme, + }), + }, + ).catch(function (err) { + console.error('Error saving the confirmation settings:', err) + }) + } + + /** + * Whether the active set is the stock one, read fresh. + * + * The dropdown can move under the editor without a reload, so the + * server-rendered flag is only the starting point. + * + * @return {boolean} True when the stock Nextcloud set is active. + */ + function onStockSet() { + if (tokenSetSelect !== null && tokenSetSelect.value !== '') { + return tokenSetSelect.value === STOCK_TOKEN_SET + } + + return pageTokenSetId === STOCK_TOKEN_SET + } + + /** + * Put the "do not ask again" row into a dialog and report its state. + * + * @param {Element} overlay The dialog overlay. + * @param {string} which Which flag this dialog controls. + * + * @return {void} + */ + function wireDoNotAskAgain(overlay, which) { + var box = overlay.querySelector('.nldesign-dialog-dontask') + if (box === null) { + return + } + + box.addEventListener('change', function () { + if (which === 'stock') { + confirmSaveStock = box.checked === false + } else { + confirmSaveTheme = box.checked === false + } + saveConfirmFlags().then(refreshConfirmControls) + }) + } + + /** + * The markup both dialogs share for the "do not ask again" row. + * + * @return {string} The row's HTML. + */ + function dontAskAgainRow() { + return ( + '' + ) + } + + /** + * Ask before saving, and run `proceed` with the chosen route. + * + * `proceed` is called with `'new-set'` to keep the edits as a token set + * of their own, or `'overrides'` to write them over the active theme. + * Cancelling calls nothing at all — deliberately, so a dismissed dialog + * can never be mistaken for a save. + * + * @param {Function} proceed Called with the chosen route. + * + * @return {void} + */ + function confirmSave(proceed) { + var stock = onStockSet() + + if (stock === false && confirmSaveTheme === false) { + proceed('overrides') + return + } + if (stock === true && confirmSaveStock === false) { + proceed('overrides') + return + } + + if (stock === true) { + openStockSaveDialog(proceed) + return + } + + openThemeSaveDialog(proceed) + } + + /** + * The stock-set dialog: new token set, or over the Nextcloud theme. + * + * @param {Function} proceed Called with the chosen route. + * + * @return {void} + */ + function openStockSaveDialog(proceed) { + var html = + '
' + + '
' + + '

' + + escapeHtml(t('thematiq', 'Keep these changes as a new theme?')) + + '

' + + '

' + + escapeHtml( + t( + 'thematiq', + 'You are working on the stock Nextcloud theme. Save the changes as a token set of their own, or write them over the Nextcloud theme itself. Writing over it changes Nextcloud for everyone; "Reset theme to Nextcloud" brings the original back.', + ), + ) + + '

' + + '' + // Where the server's refusal lands. In the dialog, beside the + // field that caused it, because the one thing an admin needs + // after "that name is taken" is the name still being there to + // edit. A toast over a closed dialog loses the whole export. + + '' + + dontAskAgainRow() + + '
' + + '' + + '' + + '' + + '
' + + '
' + + '
' + + document.body.insertAdjacentHTML('beforeend', html) + var overlay = document.getElementById('nldesign-save-dialog-overlay') + + function close() { + overlay.remove() + } + + makeDialogAccessible(overlay, close) + wireDoNotAskAgain(overlay, 'stock') + + overlay + .querySelector('.nldesign-dialog-cancel') + .addEventListener('click', close) + overlay + .querySelector('.nldesign-dialog-overwrite') + .addEventListener('click', function () { + close() + proceed('overrides') + }) + overlay + .querySelector('.nldesign-dialog-confirm') + .addEventListener('click', function () { + var nameEl = document.getElementById('nldesign-save-newset-name') + if (nameEl === null) { + return + } + + var name = nameEl.value.trim() + if (name === '') { + // Refused here rather than at the server, because the + // name is the one thing this dialog exists to collect, + // and a round trip to be told so loses what was typed. + showError(t('thematiq', 'Give the new theme a name.')) + return + } + + // The dialog STAYS OPEN across the request and closes only + // once the set exists. A name collision is the expected + // failure here — the server answers 409 with the name it + // already has — and the admin's next move is to change one + // word and try again, which is only possible if the dialog + // and everything typed into it are still on screen. + busy(true) + showError('') + proceed('new-set', name, { + done: function () { + busy(false) + close() + }, + fail: function (message) { + busy(false) + showError(message) + }, + }) + }) + + /** + * Show, or clear, the server's refusal inside the dialog. + * + * @param {string} message The message, or '' to clear it. + * + * @return {void} + */ + function showError(message) { + var errorEl = document.getElementById('nldesign-save-newset-error') + var nameEl = document.getElementById('nldesign-save-newset-name') + if (errorEl === null) { + return + } + + errorEl.textContent = message + errorEl.hidden = message === '' + if (nameEl === null) { + return + } + + if (message === '') { + nameEl.removeAttribute('aria-invalid') + return + } + + nameEl.setAttribute('aria-invalid', 'true') + nameEl.focus() + nameEl.select() + } + + /** + * Lock the dialog's controls while the set is being created. + * + * @param {boolean} on Whether a request is in flight. + * + * @return {void} + */ + function busy(on) { + overlay + .querySelectorAll('button, input') + .forEach(function (control) { + control.disabled = on + }) + } + } + + /** + * The plain confirmation shown on every set that is not stock. + * + * @param {Function} proceed Called with the chosen route. + * + * @return {void} + */ + function openThemeSaveDialog(proceed) { + var setName = + tokenSetSelect !== null + && tokenSetsData[tokenSetSelect.value] !== undefined + ? tokenSetsData[tokenSetSelect.value].name + : tokenSetSelect !== null + ? tokenSetSelect.value + : '' + + var html = + '
' + + '
' + + '

' + + escapeHtml(t('thematiq', 'Save these overrides?')) + + '

' + + '

' + + escapeHtml( + t('thematiq', 'They are applied on top of {set}.', { + set: setName, + }), + ) + + '

' + + dontAskAgainRow() + + '
' + + '' + + '' + + '
' + + '
' + + '
' + + document.body.insertAdjacentHTML('beforeend', html) + var overlay = document.getElementById('nldesign-save-dialog-overlay') + + function close() { + overlay.remove() + } + + makeDialogAccessible(overlay, close) + wireDoNotAskAgain(overlay, 'theme') + + overlay + .querySelector('.nldesign-dialog-cancel') + .addEventListener('click', close) + overlay + .querySelector('.nldesign-dialog-confirm') + .addEventListener('click', function () { + close() + proceed('overrides') + }) + } + + /** + * The design system the page is currently wearing. + * + * Read off the selected option's `data-design-system`, which is the same + * attribute the badge reads, so the value saved into a new theme is the + * one the panel is showing the admin. + * + * @return {string} A design-system id, defaulting to nldesign. + */ + function currentDesignSystemId() { + if (tokenSetSelect === null) { + return 'nldesign' + } + + var option = tokenSetSelect.querySelector( + 'option[value="' + tokenSetSelect.value + '"]', + ) + + return option === null + ? 'nldesign' + : option.getAttribute('data-design-system') || 'nldesign' + } + + /** + * Serialise what the page is wearing into a new custom token set. + * + * Reuses the playground's exporter — the same function behind "Export as + * token set" — so a set created here and one downloaded there are the + * same file. It is posted to the custom-set upload endpoint rather than + * downloaded, which is what makes this one click instead of a round trip + * through the admin's file manager. + * + * @param {string} name The display name the admin typed. + * + * @return {void} + */ + function saveAsNewTokenSet(name, report) { + var playground = window.ThematiqPlayground + if (playground === undefined || playground.exportCss === undefined) { + report.fail( + t( + 'thematiq', + 'The theme exporter is not loaded on this page, so no new theme was created.', + ), + ) + return + } + + // `playgroundExportTokens`, NOT `playgroundTokens`. + // + // The second is what the instrument DRAWS with: the active set on top + // of css/systems/nldesign/defaults.css, so every token has a value to + // show. Exporting from it wrote all 200 of them — 115 component + // tokens of Rijkshuisstijl defaults — into a theme whose author had + // changed one colour. The first is what the set DECLARES, which for + // the stock set is resolved from the running instance rather than + // from a file. See PlaygroundStateService::getExportTokens(). + // Captured before the request, so the set is recorded against the + // design system the page was wearing when it was serialised, not + // whatever the dropdown says by the time the response lands. + var designSystem = currentDesignSystemId() + + var result = playground.exportCss( + playground.liveTokens( + loadInitialState('playgroundExportTokens', {}), + function (n, fallback) { + return readVar(n, fallback) + }, + ), + collectSavedAndDirtyOverrides(), + loadInitialState('playgroundTokenSources', {}), + ) + + // An override the exporter could not express is a value the admin + // set, saw applied, and would not find in the theme they just saved. + // The download path has always said so; this one used to drop them + // without a word, which is how a header colour went missing between + // the editor and the file. + if (result.unexpressed.length > 0) { + notify( + t( + 'thematiq', + 'These values could not be written into a theme and are kept as overrides instead: {names}', + { names: result.unexpressed.join(', ') }, + ), + ) + } + + fetch(OC.generateUrl('/apps/thematiq/settings/tokensets/upload'), { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + requesttoken: OC.requestToken, + }, + // `sourceName` is what the custom-set list shows as the origin. + // Saying it came from the editor rather than leaving it blank is + // what tells a later admin why a set exists that nobody uploaded. + // + // `raw` says this is already a token set, so the server stores it + // as it arrived instead of running it through the NLDS converter, + // whose job is to complete an unknown document and which therefore + // fills every token the file leaves out. + // + // `designSystem` is the one the page is wearing, so a theme saved + // off stock Nextcloud stays stock Nextcloud instead of coming back + // with the whole nldesign layer on top of it. Allow-listed server + // side; this is a claim, not a decision. + // + // `captureTheming` copies Nextcloud's own branding into the new + // theme as well, so it comes back whenever the theme is applied. + body: JSON.stringify({ + name: name, + content: result.css, + sourceName: t('thematiq', 'Token editor'), + raw: true, + designSystem: designSystem, + captureTheming: true, + }), + }) + .then(function (r) { + // Read the body whatever the status: the refusals worth + // showing an admin — 409 for a name already taken, 422 for a + // name with nothing to slugify — all carry their reason in + // `error`, and throwing on `!r.ok` would replace every one of + // them with "could not be created". + return r.json().then( + function (data) { + return { ok: r.ok, status: r.status, data: data } + }, + function () { + return { ok: r.ok, status: r.status, data: {} } + }, + ) + }) + .then(function (result) { + if (result.data.error) { + report.fail(result.data.error) + return + } + if (result.ok === false) { + report.fail( + t( + 'thematiq', + 'The new theme could not be created (HTTP {status}).', + { status: String(result.status) }, + ), + ) + return + } + + report.done() + applyCreatedTokenSet(result.data.id, name, designSystem) + rememberCapturedTheming(result.data.id, result.data.theming) + }) + .catch(function (err) { + console.error('Error creating the token set:', err) + report.fail(t('thematiq', 'The new theme could not be created.')) + }) + } + + /** + * Put a just-created set in the dropdown, then make it the live theme. + * + * Saving a theme IS choosing it. The admin has just described the thing + * they want the instance to look like and named it; leaving the instance + * on the theme they were editing away from, with a notice telling them to + * go and pick it, made a successful save look like a failed one. + * + * The set is brand new, so the dropdown has no option for it — the server + * rendered that list before it existed. It is inserted here rather than by + * re-rendering the panel, because a re-render would drop every unsaved row + * in the editor. + * + * No apply dialog either. That dialog exists to show what CHANGES when you + * move between two themes; here the page is already wearing these values + * (the editor has been previewing them inline all along), so the only work + * left is to make the server agree and swap the stylesheets under it. + * + * @param {string} id The new set's id, as the server assigned it. + * @param {string} name The display name the admin typed. + * + * @return {void} + */ + function applyCreatedTokenSet(id, name, designSystem) { + if (typeof id !== 'string' || id === '') { + // Created, but we cannot name it — say so rather than silently + // leaving the instance on the old theme. + notify( + t( + 'thematiq', + 'Theme "{name}" was created but could not be applied. Pick it in the theme dropdown.', + { name: name }, + ), + ) + return + } + + // The set was saved carrying the design system the page was wearing, + // so the dropdown entry and the badge say the same thing the server + // stored — a theme made from stock Nextcloud reads as stock, not as + // NL Design System. + tokenSetsData[id] = { id: id, name: name, design_system: designSystem } + + if ( + tokenSetSelect !== null + && tokenSetSelect.querySelector('option[value="' + id + '"]') + === null + ) { + var option = document.createElement('option') + option.value = id + option.textContent = name + option.dataset.designSystem = designSystem + tokenSetSelect.appendChild(option) + } + + commitTokenSetChange(id, false) + .then(function (data) { + if (data.status !== 'ok') { + throw new Error(data.error || 'Token set change failed') + } + + // reflectSelection() moves the dropdown WITHOUT opening the + // apply dialog, which its own change handler would. + reflectSelection(id) + + return applyLayersFor(id) + }) + .then(function (swapped) { + notify( + swapped === true + ? t('thematiq', 'Theme "{name}" created and applied.', { + name: name, + }) + : t( + 'thematiq', + 'Theme "{name}" created and set as the active theme. Reload the page to see it.', + { name: name }, + ), + ) + }) + .catch(function (err) { + console.error('Error applying the new token set:', err) + notify( + t( + 'thematiq', + 'Theme "{name}" was created but could not be applied. Pick it in the theme dropdown.', + { name: name }, + ), + ) + }) + } + + /** + * The dirty tokens, in the shape the overrides endpoint stores. + * + * @return {Object} Token name → value. + */ + function collectOverrides() { var overrides = {} Object.keys(tokenEditorState).forEach(function (name) { var state = tokenEditorState[name] @@ -2614,6 +3930,101 @@ } }) + return overrides + } + + /** + * The saved overrides with the unsaved edits on top. + * + * collectOverrides() is what the editor has CHANGED, so a value saved + * earlier — which reads back as the live value — is not in it. A new + * theme made from here has to carry both, or the edits an admin saved + * over the Nextcloud theme would be missing from the theme made of it. + * + * @return {Object} Token name → value. + */ + function collectSavedAndDirtyOverrides() { + var overrides = {} + Object.keys(tokenEditorState).forEach(function (name) { + var custom = tokenEditorState[name].custom + if (typeof custom === 'string' && custom.trim() !== '') { + overrides[name] = custom.trim() + } + }) + + return Object.assign(overrides, collectOverrides()) + } + + /** + * The whole overrides file as it should be after this save. + * + * The endpoint REPLACES the file, so what is sent has to be every value + * that stays saved, not only what changed. collectOverrides() alone was + * not that: a value saved earlier is on the page after a reload, reads + * back as `resolved`, and so never differed from it — every save after a + * reload therefore deleted every earlier one, and each component whose + * own colour had been saved fell back to the primary colour. + * + * An edited row sends its value, and clearing its field is how a value + * stops being saved. A row nobody touched keeps what is stored. + * + * @return {Object} Token name → value. + */ + function collectOverridesToWrite() { + var overrides = {} + Object.keys(tokenEditorState).forEach(function (name) { + var state = tokenEditorState[name] + var saved = + typeof state.custom === 'string' && state.custom.trim() !== '' + ? state.custom.trim() + : null + if (state.isDirty !== true) { + if (saved !== null) { + overrides[name] = saved + } + return + } + var value = state.current.trim() + if (value !== '' && (saved !== null || value !== state.resolved)) { + overrides[name] = value + } + }) + + return overrides + } + + /** + * Keep a theme's freshly captured branding in the page's copy of the + * set list, so the apply dialog compares against what was just saved + * rather than what the page was rendered with. + * + * @param {string} id The token set id. + * @param {Object|undefined} theming The captured block the server returned. + * @return {void} + */ + function rememberCapturedTheming(id, theming) { + if (!id || !theming || typeof theming !== 'object') { + return + } + if (tokenSetsData[id] === undefined) { + tokenSetsData[id] = { id: id } + } + tokenSetsData[id].theming = theming + } + + function saveOverrides() { + confirmSave(function (route, name, report) { + if (route === 'new-set') { + saveAsNewTokenSet(name, report) + return + } + writeOverrides() + }) + } + + function writeOverrides() { + var overrides = collectOverridesToWrite() + var btn = document.getElementById('nldesign-save-btn') if (btn !== null) { btn.disabled = true @@ -2625,7 +4036,14 @@ 'Content-Type': 'application/json', requesttoken: OC.requestToken, }, - body: JSON.stringify({ overrides: overrides }), + // `captureTheming`: the theme also keeps Nextcloud's own branding + // as it is now (colours, background, logos, favicon), so applying + // it later puts that back. The server skips the stock set. + body: JSON.stringify({ + overrides: overrides, + tokenSet: editedTokenSetId(), + captureTheming: true, + }), }) .then(function (r) { return r.json() @@ -2635,9 +4053,27 @@ btn.disabled = false } if (data.status === 'ok') { + rememberCapturedTheming(editedTokenSetId(), data.theming) + // What was just written is now the saved value: the + // reset button returns to it, and no row is unsaved. Object.keys(tokenEditorState).forEach(function (k) { - tokenEditorState[k].isDirty = false + var state = tokenEditorState[k] + state.custom = + overrides[k] !== undefined ? overrides[k] : null + state.saved = state.current + state.isDirty = false }) + var editor = document.getElementById('nldesign-token-editor') + if (editor !== null) { + editor + .querySelectorAll('.nldesign-token-custom-badge') + .forEach(function (badge) { + badge.remove() + }) + } + // Saved, so applied: the page takes the values from the + // file it loads rather than from the preview. + refreshCustomOverridesLink() updateSaveStatus() notify(t('thematiq', 'Token overrides saved.')) } else { @@ -2658,7 +4094,7 @@ function exportOverrides() { var a = document.createElement('a') - a.href = OC.generateUrl('/apps/thematiq/settings/overrides/export') + a.href = overridesUrl('/export', editedTokenSetId()) a.download = 'custom-overrides.css' document.body.appendChild(a) a.click() @@ -2669,7 +4105,7 @@ var formData = new FormData() formData.append('file', file) - fetch(OC.generateUrl('/apps/thematiq/settings/overrides/import'), { + fetch(overridesUrl('/import', editedTokenSetId()), { method: 'POST', headers: { requesttoken: OC.requestToken }, body: formData, @@ -3028,33 +4464,49 @@ } }) - fetch(OC.generateUrl('/apps/thematiq/settings/overrides'), { - headers: { requesttoken: OC.requestToken }, - }) - .then(function (r) { - return r.json() - }) - .then(function (existingData) { - var merged = Object.assign( - {}, - existingData.overrides || {}, - toApply, - ) - return fetch( - OC.generateUrl('/apps/thematiq/settings/overrides'), - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - requesttoken: OC.requestToken, - }, - body: JSON.stringify({ overrides: merged }), - }, - ) - }) - .then(function (r) { - return r.json() - }) + // Nothing is pinned into the stock set. Its overrides file is + // the Nextcloud theme ITSELF being changed, which only the + // editor does and only past its warning; values pinned here on + // the way back to stock are what used to keep a stale blue on + // the page through every reset. + var pinned = + newTokenSetId === STOCK_TOKEN_SET + ? Promise.resolve({ status: 'ok' }) + : fetch(overridesUrl('', newTokenSetId), { + headers: { requesttoken: OC.requestToken }, + }) + .then(function (r) { + return r.json() + }) + .then(function (existingData) { + var merged = Object.assign( + {}, + existingData.overrides || {}, + toApply, + ) + return fetch( + OC.generateUrl( + '/apps/thematiq/settings/overrides', + ), + { + method: 'POST', + headers: { + 'Content-Type': + 'application/json', + requesttoken: OC.requestToken, + }, + body: JSON.stringify({ + overrides: merged, + tokenSet: newTokenSetId, + }), + }, + ) + }) + .then(function (r) { + return r.json() + }) + + pinned .then(function (saveData) { if (saveData.status !== 'ok') { throw new Error(saveData.error || 'Save failed') @@ -4273,6 +5725,61 @@ // Initialise the custom token set panel on page load. initCustomTokenSets() + /* ========================================================================== + * RESET THEME TO NEXTCLOUD + * + * The way back to stock: the active theme's overrides and the edits + * written over the Nextcloud theme are emptied, the stock set becomes + * active and core theming is reset (OverridesController::resetToStock()). + * The page reloads afterwards, because every panel on it — the editor, + * the core-theming fields, the stylesheet run — describes what was reset. + * ========================================================================== */ + + var resetThemeBtn = document.getElementById('nldesign-reset-theme-btn') + if (resetThemeBtn !== null) { + resetThemeBtn.addEventListener('click', function () { + OC.dialogs.confirm( + t( + 'thematiq', + 'This fully resets the theme to stock Nextcloud: the overrides of the current theme and every change written over the Nextcloud theme are deleted, and the Nextcloud theming colours and logo return to their defaults. Custom token sets, fonts and custom CSS are kept. This cannot be undone.', + ), + t('thematiq', 'Reset theme to Nextcloud'), + function (confirmed) { + if (confirmed !== true) { + return + } + + resetThemeBtn.disabled = true + fetch(OC.generateUrl('/apps/thematiq/settings/overrides'), { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + requesttoken: OC.requestToken, + }, + body: JSON.stringify({ reset: true }), + }) + .then(function (r) { + return r.json() + }) + .then(function (data) { + if (data.status !== 'ok') { + throw new Error(data.error || 'Reset failed') + } + window.location.reload() + }) + .catch(function (err) { + resetThemeBtn.disabled = false + console.error('Error resetting the theme:', err) + notify( + t('thematiq', 'The theme could not be reset.'), + ) + }) + }, + true, + ) + }) + } + /* ========================================================================== * CUSTOM FONTS (admin-uploaded, self-hosted webfonts) * diff --git a/js/lib/layerSwap.js b/js/lib/layerSwap.js index f6067747..637488e2 100644 --- a/js/lib/layerSwap.js +++ b/js/lib/layerSwap.js @@ -178,7 +178,7 @@ function insertBeforeAnchor(doc) { return ( doc.querySelector( - 'link[rel="stylesheet"][href*="/thematiq/css/custom-overrides.css"]', + 'link[rel="stylesheet"][href*="/thematiq/css/custom-overrides"]', ) || doc.querySelector('link[rel="stylesheet"].theme') || null diff --git a/js/lib/tokenTransforms.js b/js/lib/tokenTransforms.js index 3caa57ad..2d5e75d6 100644 --- a/js/lib/tokenTransforms.js +++ b/js/lib/tokenTransforms.js @@ -83,7 +83,8 @@ /** * Normalise a colour value into a #RRGGBB string suitable for an * `` picker. Handles #RRGGBB (passthrough), #RGB - * (expanded), and empty/invalid (→ #000000). Named colours / other CSS + * (expanded), an `r, g, b` triplet (the shape the `-rgb` tokens take), + * and empty/invalid (→ #000000). Named colours / other CSS * colour syntaxes are NOT resolved here (admin.js does that via a canvas in * the browser); this pure helper returns the safe fallback for them. * @@ -101,9 +102,51 @@ if (/^#[0-9a-fA-F]{3}$/.test(v) === true) { return ('#' + v[1] + v[1] + v[2] + v[2] + v[3] + v[3]).toLowerCase() } + var triplet = /^(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})$/.exec(v) + if (triplet !== null) { + return ( + '#' + + triplet + .slice(1) + .map(function (channel) { + return ( + '0' + Math.min(255, Number(channel)).toString(16) + ).slice(-2) + }) + .join('') + ) + } return null } + /** + * A colour as the `r, g, b` triplet the `-rgb` tokens hold. + * + * Nextcloud 32 mixes its note-card fills from these (`rgba(var(--color- + * success-rgb), 0.1)`), so the value has to be the bare triplet, not a + * colour. The editor offers a colour picker for such a token and writes the + * triplet, so nobody has to work one out by hand. + * + * @param {?string} value A #RRGGBB / #RGB colour, or a triplet already. + * @return {?string} `r, g, b`, or null when the value is not one of those. + */ + function hexToRgbTriplet(value) { + var hex = normaliseColorForPicker(value) + if ( + hex === null + || value === undefined + || value === null + || String(value).trim() === '' + ) { + return null + } + return [1, 3, 5] + .map(function (at) { + return parseInt(hex.slice(at, at + 2), 16) + }) + .join(', ') + } + /** * Resolve the human-readable display name for a design-system id. * @@ -154,6 +197,7 @@ darkenHex: darkenHex, getPreviewColors: getPreviewColors, normaliseColorForPicker: normaliseColorForPicker, + hexToRgbTriplet: hexToRgbTriplet, designSystemLabel: designSystemLabel, groupDiagnosticsByReason: groupDiagnosticsByReason, } diff --git a/js/playground.js b/js/playground.js index 7b1b9441..264d3184 100644 --- a/js/playground.js +++ b/js/playground.js @@ -105,6 +105,14 @@ */ var POINTABLE = ['hover', 'focus'] + /** + * The pseudo-state `rowsByState()` files callout-0 tokens under: the ones + * every state of a component shares. + * + * @type {{n: number, id: string, label: string}} + */ + var SHARED_STATE = { n: 0, id: 'all', label: 'every variant' } + /** * The Nextcloud majors the header specimen can be drawn as. * @@ -274,7 +282,7 @@ * @return {Array<{state: Object, tokens: Array, fixed: Array}>} Rows per state. */ function rowsByState(component) { - return (component.states || []).map(function (state) { + var groups = (component.states || []).map(function (state) { return { state: state, tokens: (component.tokens || []).filter(function (token) { @@ -285,6 +293,18 @@ }), } }) + + // Callout 0 is a token every state shares — the note cards' and the + // toasts' corner. Filed under the first state it read as belonging to + // that one variant, so it leads the list on its own instead. + var shared = (component.tokens || []).filter(function (token) { + return token.callout === 0 + }) + if (shared.length > 0) { + groups.unshift({ state: SHARED_STATE, tokens: shared, fixed: [] }) + } + + return groups } /** @@ -368,7 +388,23 @@ // not knowable until every override that reads the same token is in. var unexpressed = [] var claims = {} + var direct = {} Object.keys(overrides || {}).forEach(function (name) { + // ALREADY A TOKEN, so there is nothing to map. + // + // `sources` runs Nextcloud variable => token, and every override + // used to be a Nextcloud variable: the editor exposed `--color-*` + // and nothing else. The component layer changed that — a row for + // `--nldesign-component-header-background-color` sets the token + // itself — and those names have no entry in a map keyed by + // `--color-*`, so every one of them was reported as inexpressible + // and left out of the file. An admin who set a header colour and + // saved it as a theme got a theme with no header colour in it. + if (name.indexOf('--nldesign-') === 0) { + direct[name] = (overrides || {})[name] + return + } + var token = (sources || {})[name] if (!token) { unexpressed.push(name) @@ -392,6 +428,15 @@ }) }) + // Applied after the mapped ones, so a token an admin set BY NAME beats + // the same token reached through a Nextcloud variable. Nothing in the + // registry makes both reachable today — the brand rows are `--color-*` + // and the component rows are `--nldesign-component-*` — but the rule + // wants stating rather than depending on those two lists never meeting. + Object.keys(direct).forEach(function (token) { + merged[token] = direct[token] + }) + var lines = Object.keys(merged) .sort() .map(function (token) { @@ -640,11 +685,12 @@ // The set the page is WEARING, which a session preview changes — // so an export is named after the set it actually contains. tokenSet: loadState('playgroundSet', ''), - // The version this instance runs, and the one the header specimen - // is currently drawn as. They start equal: the version you are on - // is the one you are asking about first. + // The version this instance runs, and per component the one it is + // currently drawn as. Each starts on the running version — the one + // you are on is the one you are asking about first — and choosing + // another for one component leaves every other component alone. serverVersion: loadState('playgroundVersion', 0), - headerVersion: headerMajor(loadState('playgroundVersion', 0)), + versions: {}, editor: editor, preview: preview, tabs: tabs, @@ -686,6 +732,21 @@ title.appendChild(state.crumb) } + // The app mock's typography link is a real `` as well, for its + // hover and focus; following it would rewrite the hash the selection + // lives in. Bound once, because build() runs again on every re-render. + if (preview.hasAttribute('data-pg-links') === false) { + preview.setAttribute('data-pg-links', '') + preview.addEventListener('click', function (event) { + if ( + typeof event.target.closest === 'function' + && event.target.closest('.nl-mini a[href]') !== null + ) { + event.preventDefault() + } + }) + } + // The third stage, next to the app and login views. state.stage = el('div', 'nldesign-preview-stage nldesign-pg-stage') state.stage.setAttribute('data-view', 'component') @@ -807,6 +868,10 @@ }) exitComponent(state) + // The app-shell and login previews draw a different part of themselves + // for each tab (css/admin.css keys on this), so a tab's full view shows + // that tab's components rather than one picture shared by all of them. + state.preview.setAttribute('data-pg-tab', tab) showView(state, tab === 'login' ? 'login' : 'app') renderChips(state) updateCrumb(state) @@ -938,8 +1003,28 @@ var filtered = el('div', 'nldesign-pg-panel') filtered.appendChild(panelHead(state, component)) + var groupsDrawn = 0 rowsByState(component).forEach(function (group) { + if (group.tokens.length === 0 && group.fixed.length === 0) { + return + } + // A component made of variants — one card or toast per type — puts + // a rule between them, so it is clear which rows belong together. + if (component.separateStates === true && groupsDrawn > 0) { + filtered.appendChild(el('hr', 'nldesign-pg-rowsep')) + } + groupsDrawn++ group.tokens.forEach(function (spec) { + // A token only one version reads — the note cards' `-rgb` fills + // exist on Nextcloud 32 alone — is offered while that version is + // the one drawn, and nowhere else: a row that moves nothing on + // the instance being themed is a control that does nothing. + if ( + spec.until !== undefined + && versionOf(state, component) > spec.until + ) { + return + } filtered.appendChild(cloneRow(state, spec)) }) group.fixed.forEach(function (spec) { @@ -1067,20 +1152,38 @@ // Mirroring only picker → text left the swatch beside a // hand-typed hex showing the previous colour, which is the one // thing in the row that is not a number an admin can check. + // On an `rgb` row the picker reports #RRGGBB but the token holds + // the bare `r, g, b` triplet, so the value is converted once here + // and everything below writes the converted one. + var value = input.value + var transforms = window.NldesignTokenTransforms || {} + if ( + input.type === 'color' + && input.dataset.format === 'rgb' + && typeof transforms.hexToRgbTriplet === 'function' + ) { + value = transforms.hexToRgbTriplet(input.value) || input.value + } + var text = row.querySelector('.nldesign-color-text') if (input.type === 'color' && text !== null) { - text.value = input.value + text.value = value } var swatch = row.querySelector('.nldesign-color-picker') - if ( - input.type === 'text' - && swatch !== null - && /^#[0-9a-fA-F]{6}$/.test(input.value.trim()) === true - ) { - swatch.value = input.value.trim() + if (input.type === 'text' && swatch !== null) { + var typed = input.value.trim() + if (/^#[0-9a-fA-F]{6}$/.test(typed) === true) { + swatch.value = typed + } else if ( + swatch.dataset.format === 'rgb' + && typeof transforms.normaliseColorForPicker === 'function' + && transforms.normaliseColorForPicker(typed) !== null + ) { + swatch.value = transforms.normaliseColorForPicker(typed) + } } - state.preview.style.setProperty(spec.name, input.value) + state.preview.style.setProperty(spec.name, value) // markDirty() puts the custom-value badge on the row it finds // by `data-token-row`, which is always the ORIGINAL — and the @@ -1143,8 +1246,10 @@ reset.addEventListener('click', function (event) { event.preventDefault() + // The real reset also puts the preview back — on the saved value when + // there is one — and `state.preview` is the element it writes on. + // Clearing the property here as well threw that saved value away. originalReset.click() - state.preview.style.removeProperty(spec.name) // After the real reset has run: whatever it put back is the truth. window.setTimeout(function () { @@ -1405,6 +1510,26 @@ function renderStage(state, component) { state.stage.innerHTML = '' + // Name the component this stage is drawing, so the theme can reach the + // specimen the way it reaches the real thing. + // + // A specimen carries the component's real CLASS names, which is enough + // for every component whose own rules are class-scoped. It is not + // enough for the four whose rules are id-scoped — `#header`, and the + // three under `#body-login` — because a specimen cannot carry an id + // that belongs to the page it is drawn on. The login button is the + // visible case: its specimen is the same `.button-vue--primary` markup + // as the primary button's, so without this hook it was painted by the + // PRIMARY button's token and an admin editing the login colour saw + // nothing move. + // + // css/component-scopes.css lists `[data-thematiq-component='']` + // beside each component's real selectors, so the specimen resolves the + // same tokens the real component does. Nothing in the design-system + // stylesheets knows about the playground; the redirect is entirely in + // the generated scope layer. + state.stage.setAttribute('data-thematiq-component', component.id) + // Drop the scope map so this stage is stamped from the sheets that are // loaded NOW. See ensureScopes(): a map read while one chunk was still // in flight is partial rather than empty, and keeping it leaves that @@ -1428,9 +1553,13 @@ var build = STAGES[component.id] - // The header is the one component whose MARKUP differs between the - // versions this app supports, so it gets to be drawn as any of them. - if (component.id === 'header-bar') { + // A component that differs between the supported versions can be asked + // about as any of them: `versionNotes` in js/playground/components.json + // lists what differs. The header and the note cards are also DRAWN as + // the chosen version; for the others the stage says what that version + // does differently. A component every version draws the same gets no + // switch, because there would be nothing to switch. + if (hasVersions(component) === true) { state.stage.appendChild(versionSwitch(state, component)) } @@ -1456,7 +1585,7 @@ var wide = el('div', 'nldesign-pg-wide') wide.innerHTML = typeof build === 'function' - ? build(null, component, state.headerVersion) + ? build(null, component, versionOf(state, component)) : fallbackSample() scopeSpecimen(wide) ground.appendChild(wide) @@ -1530,6 +1659,28 @@ ) } + // Nextcloud 32 and 33 draw the app menu from white icon IMAGES, and a + // colour cannot reach an image — only a filter can, and Nextcloud picks + // that filter from its own background colour, not from the header's. + // Said on the stage, because the specimen is right and still looks like + // the glyph token is broken. + if (component.id === 'header-bar' && versionOf(state, component) < 34) { + state.stage.appendChild( + el( + 'div', + 'nldesign-pg-pointable', + t( + 'thematiq', + 'On Nextcloud 32 and 33 the app icons are images, so "Header glyphs" does not recolour them: they stay white. The other header glyphs do follow it. From Nextcloud 34 the app menu follows it too.', + ), + ), + ) + } + + versionNotes(state, component).forEach(function (line) { + state.stage.appendChild(el('div', 'nldesign-pg-pointable', line)) + }) + var saidLine = el('div', 'nldesign-pg-say') saidLine.setAttribute('aria-live', 'polite') state.stage.appendChild(saidLine) @@ -1600,7 +1751,7 @@ var button = el( 'button', 'nldesign-pg-version' - + (version === headerMajor(state.headerVersion) ? ' on' : '') + + (version === versionOf(state, component) ? ' on' : '') + (running ? ' is-running' : ''), String(version), ) @@ -1609,7 +1760,14 @@ button.title = t('thematiq', 'The version this instance is running') } button.addEventListener('click', function () { - state.headerVersion = version + state.versions[component.id] = version + // A versioned component can offer different rows per version, + // so the panel is rebuilt with the stage. The header only + // changes its drawing. + if (component.versioned === true) { + enterComponent(state, component) + return + } renderStage(state, component) }) row.appendChild(button) @@ -1618,6 +1776,84 @@ return row } + /** + * The version a component is drawn as: the one chosen for it, else the one + * this instance runs. + * + * @param {Object} state The instrument state. + * @param {Object} component The component. + * @return {number} A supported major version. + */ + function versionOf(state, component) { + var chosen = state.versions[component.id] + return headerMajor(chosen === undefined ? state.serverVersion : chosen) + } + + /** + * Whether a component differs between the supported versions at all. + * + * @param {Object} component The component. + * @return {boolean} True when it has version notes, or is drawn per version. + */ + function hasVersions(component) { + return ( + (component.versionNotes || []).length > 0 + || component.id === 'header-bar' + || component.versioned === true + ) + } + + /** + * What the stage says about the version a component is drawn as. + * + * `versionNotes` in js/playground/components.json holds what a version + * does differently, read off each version's own component stylesheets. A + * component with none is drawn the same by every supported version and has + * no switch, so nothing is said. Only + * the header and the note cards are also DRAWN as another version; for the + * rest the preview keeps the styles of the version this instance runs, and + * says so while another one is chosen. + * + * @param {Object} state The instrument state. + * @param {Object} component The component. + * @return {Array} The lines to show. + */ + function versionNotes(state, component) { + var version = versionOf(state, component) + var running = headerMajor(state.serverVersion) + var notes = component.versionNotes || [] + var lines = [] + + if (hasVersions(component) === false) { + return lines + } + + notes.forEach(function (note) { + if (note.versions.indexOf(version) !== -1) { + lines.push( + t('thematiq', 'Nextcloud {version}:', { + version: String(version), + }) + + ' ' + + note.text, + ) + } + }) + + var drawn = component.id === 'header-bar' || component.versioned === true + if (version !== running && drawn === false) { + lines.push( + t( + 'thematiq', + 'The preview keeps the styles of Nextcloud {running}, the version this instance runs.', + { running: String(running) }, + ), + ) + } + + return lines + } + /** * Wire the stage once: marker tooltips, and specimens that answer back. * @@ -1639,6 +1875,15 @@ }) stage.addEventListener('click', function (event) { + // A specimen link is a real ``, for its hover and focus. + // Following it would rewrite the hash this instrument keeps its + // selection in. + if ( + typeof event.target.closest === 'function' + && event.target.closest('a[href]') !== null + ) { + event.preventDefault() + } pressFrom(stage, event.target) }) @@ -2096,10 +2341,16 @@ trailing: revealEye(), }) + choice('checkbox', true, t('thematiq', 'Remember me')) + // Named as the login button, because on the real page it IS one: + // `#body-login .button-vue--primary` puts it in the login button's + // scope. This stage is named `login-card`, so without the hook the + // submit resolved the PRIMARY button's token, and a login colour — + // saved or still being edited — never reached the card. + button('primary', 'default', t('thematiq', 'Log in'), { icon: submitArrow(), wide: true, done: t('thematiq', 'Signing in …'), + attrs: ' data-thematiq-component="login-button"', }) + '' + '' @@ -2417,8 +2668,10 @@ }, 'settings-section': function () { return ( - '
' - + '

' + // NcSettingsSection's own class names, so the section scope + // reaches the heading as it does on a real settings page. + '
' + + '

' + t('thematiq', 'Background and colours') + '' + '

' @@ -2434,17 +2687,23 @@ ) }, 'text-input': function (state) { + // A placeholder, so the empty field shows the placeholder colour + // the text input's own rows set. return field( state === 'invalid' ? t('thematiq', 'Email address') : t('thematiq', 'Account name'), state, + { placeholder: t('thematiq', 'e.g. j.jansen') }, ) }, select: function () { return ( + // The chosen value in NcSelect's pill, then the open list, whose + // options answer the pointer the way the real dropdown does. '
' - + 'Nederland' + + '' + + 'Nederland' + '' + '
    ' + '
  • België
  • ' @@ -2466,10 +2725,19 @@ + '
' ) }, - textarea: function () { + textarea: function (state) { + // Empty, so its placeholder shows; invalid, filled in and edged in + // the invalid colour. Focus is the admin's own: click into it. + var invalid = state === 'invalid' return ( - '' - + t('thematiq', 'A longer explanation, over several lines.') + '' + + (invalid + ? t('thematiq', 'A longer explanation, over several lines.') + : '') + '' ) }, @@ -2542,7 +2810,11 @@ 'success-button': function (state) { return button('success', state, t('thematiq', 'Approve')) }, - 'note-cards': function () { + 'note-cards': function (state, component, version) { + // Nextcloud 32 (@nextcloud/vue 8) mixed the fill from the `-rgb` + // token at 10% and drew the stripe in the type colour; 33 and later + // fill the card with the type colour itself. + var legacy = headerMajor(version) <= 32 ? ' is-v32' : '' return [ [ 'info', @@ -2573,6 +2845,7 @@ + def[0] + ' nldesign-pg-note is-' + def[0] + + legacy + '">' + '' + '' @@ -2602,7 +2875,11 @@ ] .map(function (def) { return ( - '
' + def[2] @@ -2641,16 +2918,19 @@ ) }, link: function () { + // Real links, so hover and keyboard focus are the browser's own + // states and the page's link rules reach them as they reach any + // link. bindStage() keeps a click from leaving the page. return ( '

' + t('thematiq', 'Read more about the NL Design System in the ') - + '' + + '' + t('thematiq', 'documentation') - + '' + + '' + t('thematiq', ', or look at the ') - + '' + + '' + t('thematiq', 'example themes') - + '' + + '' + '.

' ) }, @@ -3346,7 +3626,8 @@ * @param {string} label The field label. * @param {string} state default, focus or invalid. * @param {Object} [options] `inside` places the label over the input, - * `trailing` is markup for a trailing button, `type` is the input type. + * `trailing` is markup for a trailing button, `type` is the input type, + * `placeholder` is the hint an empty field shows. * @return {string} The markup. */ function field(label, state, options) { @@ -3374,7 +3655,9 @@ + id + '" class="input-field__input" type="' + (settings.type || 'text') - + '" placeholder="" aria-live="polite" value="' + + '" placeholder="' + + attr(settings.placeholder || '') + + '" aria-live="polite" value="' + (filled ? 'Ingevulde waarde' : '') + '">' + (inside diff --git a/js/playground/components.json b/js/playground/components.json index f259c19f..a236159f 100644 --- a/js/playground/components.json +++ b/js/playground/components.json @@ -1,6 +1,41 @@ { - "$comment": "Component inventory for the selector / stage / tokens instrument inside the admin panel's token editor (/settings/admin/theming). One entry per component chip, covering the @nextcloud/vue components a token set actually repaints. `tab` is the token editor tab the chip appears under; `tokens` are REAL registry tokens, and the instrument clones the editor's own row for each so edits keep flowing through the editor's dirty tracking and Save; `fixed` are facts with no token at all, carrying a reason code from the converter's vocabulary; `states` are what the stage draws, and a token's `callout` points at the state it paints. `layout: wide` means the component cannot be judged from a swatch — a table, a list, a dialog — so the stage draws ONE full-width specimen carrying every callout instead of a cell per state. `classes` lists only the class names THEMATIQ ITSELF styles for that component: the specimen markup uses the full real class names, but this list is the drift guard, and a name here that no shipped stylesheet mentions means the theme has stopped reaching the component. The check deliberately EXCLUDES css/playground*.css: the specimen fallback floor defines every class the specimen draws, so counting it lets the drawing answer for the theme — which is how .menutoggle, .unified-search__button and .app-content-detail all passed this guard while being names Nextcloud does not emit. tests/vitest/playgroundInventory.spec.js enforces all of it.", - "version": 3, + "$comment": [ + "Component inventory for the selector / stage / tokens instrument inside the admin panel's token", + "editor (/settings/admin/theming). One entry per component chip, covering the @nextcloud/vue", + "components a token set actually repaints. `tab` is the token editor tab the chip appears under.", + "", + "`tokens` ARE COMPONENT-SCOPED, AND THAT IS THE POINT. Every name here is a", + "`--nldesign-component-*` token defined in scripts/mapping/component-tokens.json, not one of", + "Nextcloud's globals. Until version 4 these chips named the globals directly, so `Primary button`", + "wrote `--color-primary-element` — the variable that also paints the selected navigation entry,", + "the sidebar's active tab, a focused text input, the checked checkbox, the progress bar, the", + "dialog confirm button and the counter bubble. Moving the button moved all eight. The mapping", + "file now gives each chip its own token and css/component-scopes.css re-scopes the global inside", + "that component's subtree, so a chip reaches its own component and nothing else.", + "", + "The globals themselves are still editable, from the editor's four-tab list rather than from a", + "chip: moving one is MEANT to move every component that has not been given a value of its own.", + "They are listed as `brandTokens` in the mapping file, and tests/vitest/playgroundInventory.spec.js", + "reads that list so its coverage guard stays honest about what a chip is allowed not to cover.", + "", + "The instrument clones the editor's own row for each token, so edits keep flowing through the", + "editor's dirty tracking and Save — including the `disabled` a row carries while the", + "`primary_drives_components` setting owns it.", + "", + "`fixed` are facts with no token at all, carrying a reason code from the converter's vocabulary;", + "`states` are what the stage draws, and a token's `callout` points at the state it paints.", + "`layout: wide` means the component cannot be judged from a swatch — a table, a list, a dialog —", + "so the stage draws ONE full-width specimen carrying every callout instead of a cell per state.", + "", + "`classes` lists only the class names THEMATIQ ITSELF styles for that component: the specimen", + "markup uses the full real class names, but this list is the drift guard, and a name here that no", + "shipped stylesheet mentions means the theme has stopped reaching the component. The check", + "deliberately EXCLUDES css/playground*.css: the specimen fallback floor defines every class the", + "specimen draws, so counting it lets the drawing answer for the theme — which is how", + ".menutoggle, .unified-search__button and .app-content-detail all passed this guard while being", + "names Nextcloud does not emit. tests/vitest/playgroundInventory.spec.js enforces all of it." + ], + "version": 4, "tabs": [ { "id": "login", @@ -54,19 +89,40 @@ "label": "hovered app entry" } ], + "versionNotes": [ + { + "versions": [ + 32, + 33 + ], + "text": "the app menu is a row of app icons." + }, + { + "versions": [ + 34 + ], + "text": "the app menu is a waffle button and the current app." + }, + { + "versions": [ + 35 + ], + "text": "as 34, with a centre column: the search is a field and the waffle and the current app share one highlight." + } + ], "tokens": [ { - "name": "--color-primary", + "name": "--nldesign-component-header-background-color", "paints": "The header, when the set declares no header colour", "callout": 1 }, { - "name": "--color-primary-text", + "name": "--nldesign-component-header-color", "paints": "App icons, search and the account glyphs", "callout": 1 }, { - "name": "--color-background-hover", + "name": "--nldesign-component-header-hover-background-color", "paints": "Hovered app entry", "callout": 2 } @@ -124,9 +180,14 @@ ], "tokens": [ { - "name": "--color-primary-element", - "paints": "The Log in button", - "callout": 2 + "name": "--nldesign-component-login-box-border-radius", + "paints": "Its corner", + "callout": 1 + }, + { + "name": "--nldesign-component-login-box-border-color", + "paints": "Its border", + "callout": 1 } ], "fixed": [ @@ -182,24 +243,32 @@ "label": "background on hover" } ], + "versionNotes": [ + { + "versions": [ + 32 + ], + "text": "labels can also take the light and lighter text colours; from 33 they do not." + } + ], "tokens": [ { - "name": "--color-primary-element", + "name": "--nldesign-component-login-button-background-color", "paints": "Background", "callout": 1 }, { - "name": "--color-primary-element-text", + "name": "--nldesign-component-login-button-color", "paints": "Label", "callout": 1 }, { - "name": "--color-primary-element-hover", + "name": "--nldesign-component-login-button-hover-background-color", "paints": "Background on hover", "callout": 2 }, { - "name": "--border-radius-element", + "name": "--nldesign-component-login-button-border-radius", "paints": "Corner", "callout": 1 } @@ -212,7 +281,10 @@ "title": "Logo & slogan", "subtitle": "the branding above the login card", "ground": "login", - "classes": ["logo", "header-guest"], + "classes": [ + "logo", + "header-guest" + ], "states": [ { "n": 1, @@ -222,12 +294,12 @@ ], "tokens": [ { - "name": "--color-primary-text", + "name": "--nldesign-component-logo-slogan-color", "paints": "Slogan text", "callout": 1 }, { - "name": "--font-face", + "name": "--nldesign-component-logo-slogan-font-family", "paints": "Slogan typeface", "callout": 1 } @@ -247,7 +319,9 @@ "title": "Background", "subtitle": "the page behind everything", "ground": "login", - "classes": ["body-login-container"], + "classes": [ + "body-login-container" + ], "states": [ { "n": 1, @@ -302,19 +376,33 @@ "label": "hovered entry" } ], + "versionNotes": [ + { + "versions": [ + 32 + ], + "text": "parts of the navigation use fully rounded (pill) corners where 33 and later use the element corner." + }, + { + "versions": [ + 35 + ], + "text": "entries animate their hover and active change." + } + ], "tokens": [ { - "name": "--border-radius-element", + "name": "--nldesign-component-navigation-border-radius", "paints": "Entry corners", "callout": 1 }, { - "name": "--color-main-text", + "name": "--nldesign-component-navigation-color", "paints": "Entry labels and icons, the selected one included", "callout": 1 }, { - "name": "--color-primary-element", + "name": "--nldesign-component-navigation-active-background-color", "paints": "The selected entry's wash and its marker stripe", "callout": 2 } @@ -369,24 +457,24 @@ ], "tokens": [ { - "name": "--body-container-radius", + "name": "--nldesign-component-content-card-container-border-radius", "paints": "Container corner", "callout": 1 }, { - "name": "--border-radius-rounded", + "name": "--nldesign-component-content-card-border-radius", "paints": "Rounded surfaces inside it", "callout": 1 }, { - "name": "--color-border", + "name": "--nldesign-component-content-card-border-color", "paints": "The rule between list and detail", "callout": 1 }, { - "name": "--color-scrollbar", + "name": "--nldesign-component-content-card-scrollbar-color", "paints": "Scrollbar thumb", - "callout": 2 + "callout": 1 } ], "fixed": [ @@ -425,31 +513,41 @@ ], "tokens": [ { - "name": "--color-text-maxcontrast", + "name": "--nldesign-component-table-header-color", "paints": "Header row labels and secondary cells", "callout": 1 }, { - "name": "--color-border", + "name": "--nldesign-component-table-header-background-color", + "paints": "The header row's fill", + "callout": 1 + }, + { + "name": "--nldesign-component-table-header-font-weight", + "paints": "How bold the header labels are", + "callout": 1 + }, + { + "name": "--nldesign-component-table-border-color", "paints": "The rule under each row", "callout": 1 }, { - "name": "--color-background-dark", + "name": "--nldesign-component-table-zebra-background-color", "paints": "Zebra row", - "callout": 2 + "callout": 1 }, { - "name": "--color-background-hover", + "name": "--nldesign-component-table-row-hover-background-color", "paints": "Hovered row", - "callout": 3 + "callout": 2 } ], "fixed": [ { "callout": 1, "what": "Row height and column spacing", - "why": "A token set is a reskin: it carries colours and leaves table geometry and header weight to Nextcloud.", + "why": "A token set is a reskin: it carries colours and leaves table geometry to Nextcloud.", "code": "geometry-kept-nextcloud" } ] @@ -461,7 +559,9 @@ "subtitle": "NcAppSidebar with its tab strip", "ground": "plain", "layout": "wide", - "classes": ["app-sidebar"], + "classes": [ + "app-sidebar" + ], "radiusToken": "--border-radius-large", "states": [ { @@ -475,24 +575,45 @@ "label": "active tab" } ], + "versionNotes": [ + { + "versions": [ + 32 + ], + "text": "the sidebar also paints with the dark background colour, the main text colour and the element font weight, and has rounded element corners." + }, + { + "versions": [ + 33, + 34 + ], + "text": "no rounded element corners, and the primary colour only marks borders and focus." + }, + { + "versions": [ + 35 + ], + "text": "rounded element corners are back, the primary colour fills the active tab again, and changes animate." + } + ], "tokens": [ { - "name": "--color-border", + "name": "--nldesign-component-sidebar-border-color", "paints": "Its left edge and the tab strip rule", "callout": 1 }, { - "name": "--color-border-dark", + "name": "--nldesign-component-sidebar-divider-color", "paints": "The stronger divider", "callout": 1 }, { - "name": "--border-radius-large", + "name": "--nldesign-component-sidebar-border-radius", "paints": "Panel corner", "callout": 1 }, { - "name": "--color-primary-element", + "name": "--nldesign-component-sidebar-active-color", "paints": "The active tab underline", "callout": 2 } @@ -506,7 +627,11 @@ "subtitle": "NcListItem with NcAvatar and a counter", "ground": "content", "layout": "wide", - "classes": ["list-item", "avatardiv", "user-status-icon"], + "classes": [ + "list-item", + "avatardiv", + "user-status-icon" + ], "radiusToken": "--border-radius-element", "states": [ { @@ -525,39 +650,55 @@ "label": "selected row" } ], + "versionNotes": [ + { + "versions": [ + 32 + ], + "text": "a selected item gets no fill of its own." + }, + { + "versions": [ + 33, + 34, + 35 + ], + "text": "a selected item is filled with the light primary element colour (a stronger one on hover), and its text takes the main text colour." + } + ], "tokens": [ { - "name": "--border-radius-element", + "name": "--nldesign-component-list-item-border-radius", "paints": "Row corner", "callout": 1 }, { - "name": "--color-text-maxcontrast", + "name": "--nldesign-component-list-item-subline-color", "paints": "The subline", "callout": 1 }, { - "name": "--color-background-hover", + "name": "--nldesign-component-list-item-hover-background-color", "paints": "Hovered row", "callout": 2 }, { - "name": "--animation-quick", + "name": "--nldesign-component-list-item-transition-duration", "paints": "The hover transition", "callout": 2 }, { - "name": "--color-primary-light", + "name": "--nldesign-component-list-item-selected-background-color", "paints": "Selected row", "callout": 3 }, { - "name": "--color-primary-light-text", + "name": "--nldesign-component-list-item-selected-color", "paints": "Its text", "callout": 3 }, { - "name": "--color-primary-light-hover", + "name": "--nldesign-component-list-item-selected-hover-background-color", "paints": "The selected row on hover", "callout": 3 } @@ -581,17 +722,17 @@ ], "tokens": [ { - "name": "--color-main-text", + "name": "--nldesign-component-empty-content-color", "paints": "Its title", "callout": 1 }, { - "name": "--color-text-maxcontrast", + "name": "--nldesign-component-empty-content-description-color", "paints": "Its description", "callout": 1 }, { - "name": "--color-background-dark", + "name": "--nldesign-component-empty-content-icon-background-color", "paints": "The disc behind the icon", "callout": 1 } @@ -629,17 +770,17 @@ ], "tokens": [ { - "name": "--color-main-text", + "name": "--nldesign-component-actions-menu-color", "paints": "Entry labels", "callout": 1 }, { - "name": "--border-radius-large", + "name": "--nldesign-component-actions-menu-border-radius", "paints": "Panel corner", "callout": 1 }, { - "name": "--color-background-hover", + "name": "--nldesign-component-actions-menu-hover-background-color", "paints": "Hovered entry", "callout": 2 } @@ -659,7 +800,10 @@ "title": "Avatar", "subtitle": "NcAvatar with NcUserStatusIcon, on the header", "ground": "header", - "classes": ["avatardiv", "user-status-icon"], + "classes": [ + "avatardiv", + "user-status-icon" + ], "states": [ { "n": 1, @@ -674,9 +818,9 @@ ], "tokens": [ { - "name": "--color-success", + "name": "--nldesign-component-avatar-online-status-color", "paints": "The online status dot", - "callout": 2 + "callout": 1 } ], "fixed": [ @@ -710,17 +854,17 @@ ], "tokens": [ { - "name": "--color-primary", + "name": "--nldesign-component-breadcrumb-color", "paints": "A crumb that is a link", "callout": 1 }, { - "name": "--color-text-maxcontrast", + "name": "--nldesign-component-breadcrumb-separator-color", "paints": "The separators", "callout": 1 }, { - "name": "--color-background-hover", + "name": "--nldesign-component-breadcrumb-hover-background-color", "paints": "The hovered crumb", "callout": 2 } @@ -744,17 +888,17 @@ ], "tokens": [ { - "name": "--color-main-text", + "name": "--nldesign-component-settings-section-heading-color", "paints": "The section heading", "callout": 1 }, { - "name": "--color-text-maxcontrast", + "name": "--nldesign-component-settings-section-hint-color", "paints": "Its hint text", "callout": 1 }, { - "name": "--color-border", + "name": "--nldesign-component-settings-section-border-color", "paints": "The rule between sections", "callout": 1 } @@ -767,7 +911,10 @@ "title": "Text input", "subtitle": "NcTextField / NcInputField", "ground": "content", - "classes": ["input-field", "input-field__label"], + "classes": [ + "input-field", + "input-field__label" + ], "radiusToken": "--border-radius-element", "states": [ { @@ -786,34 +933,42 @@ "label": "invalid border" } ], + "versionNotes": [ + { + "versions": [ + 35 + ], + "text": "the field label is set in the small font size." + } + ], "tokens": [ { - "name": "--color-border-maxcontrast", + "name": "--nldesign-component-textbox-border-color", "paints": "Border", "callout": 1 }, { - "name": "--color-placeholder-dark", + "name": "--nldesign-component-textbox-placeholder-color", "paints": "Placeholder", "callout": 1 }, { - "name": "--color-placeholder-light", + "name": "--nldesign-component-textbox-placeholder-light-color", "paints": "Placeholder on a tinted field", "callout": 1 }, { - "name": "--border-radius-element", + "name": "--nldesign-component-textbox-border-radius", "paints": "Corner", "callout": 1 }, { - "name": "--color-primary-element", + "name": "--nldesign-component-textbox-focus-border-color", "paints": "Focused border and label", "callout": 2 }, { - "name": "--color-error", + "name": "--nldesign-component-textbox-invalid-border-color", "paints": "Invalid border", "callout": 3 } @@ -845,24 +1000,58 @@ { "n": 2, "id": "open", - "label": "hovered option" + "label": "options, one hovered" + } + ], + "versionNotes": [ + { + "versions": [ + 32 + ], + "text": "the dropdown uses the large corner radius and the lighter text colour." + }, + { + "versions": [ + 35 + ], + "text": "the dropdown was rebuilt on variables of its own: its border, hover, option and state colours, error and success text, and its animation all come from new places, so a theme made for 33 or 34 can look different here." } ], "tokens": [ { - "name": "--color-border-maxcontrast", + "name": "--nldesign-component-form-select-border-color", "paints": "Control border", "callout": 1 }, { - "name": "--border-radius-element", + "name": "--nldesign-component-form-select-border-radius", "paints": "Corner", "callout": 1 }, { - "name": "--color-background-hover", + "name": "--nldesign-component-form-select-color", + "paints": "The chosen value", + "callout": 1 + }, + { + "name": "--nldesign-component-form-select-selected-background-color", + "paints": "The pill behind the chosen value", + "callout": 1 + }, + { + "name": "--nldesign-component-form-select-option-color", + "paints": "The options in the list", + "callout": 2 + }, + { + "name": "--nldesign-component-form-select-option-hover-background-color", "paints": "Hovered option", "callout": 2 + }, + { + "name": "--nldesign-component-form-select-option-hover-color", + "paints": "The hovered option's text", + "callout": 2 } ], "fixed": [] @@ -888,19 +1077,27 @@ "label": "checked" } ], + "versionNotes": [ + { + "versions": [ + 32 + ], + "text": "the switch track uses the dark background colour and the element corner, and animates; from 33 it does neither." + } + ], "tokens": [ { - "name": "--color-border-maxcontrast", + "name": "--nldesign-component-checkbox-border-color", "paints": "The unchecked outline", "callout": 1 }, { - "name": "--border-radius-small", + "name": "--nldesign-component-checkbox-border-radius", "paints": "The checkbox corner", "callout": 1 }, { - "name": "--color-primary-element", + "name": "--nldesign-component-checkbox-checked-background-color", "paints": "The checked mark, dot and switch track", "callout": 2 } @@ -914,24 +1111,67 @@ "subtitle": "NcTextArea", "ground": "content", "classes": [], - "radiusToken": "--border-radius", + "radiusToken": "--border-radius-element", "states": [ { "n": 1, "id": "default", - "label": "border · corner" + "label": "empty, with its placeholder" + }, + { + "n": 2, + "id": "focus", + "label": "focused border" + }, + { + "n": 3, + "id": "invalid", + "label": "invalid border" + } + ], + "versionNotes": [ + { + "versions": [ + 32 + ], + "text": "uses the large corner radius; from 33 it does not." } ], "tokens": [ { - "name": "--color-border-maxcontrast", + "name": "--nldesign-component-textarea-border-color", "paints": "Border", "callout": 1 }, { - "name": "--border-radius", + "name": "--nldesign-component-textarea-border-radius", "paints": "Corner", "callout": 1 + }, + { + "name": "--nldesign-component-textarea-color", + "paints": "The typed text", + "callout": 1 + }, + { + "name": "--nldesign-component-textarea-background-color", + "paints": "The field's fill", + "callout": 1 + }, + { + "name": "--nldesign-component-textarea-placeholder-color", + "paints": "The placeholder of an empty field", + "callout": 1 + }, + { + "name": "--nldesign-component-textarea-focus-border-color", + "paints": "Border while focused", + "callout": 2 + }, + { + "name": "--nldesign-component-textarea-invalid-border-color", + "paints": "Border when invalid", + "callout": 3 } ], "fixed": [] @@ -943,7 +1183,12 @@ "subtitle": "NcDialog / NcModal with its button row", "ground": "overlay", "layout": "wide", - "classes": ["modal-container", "modal-wrapper", "modal-mask", "dialogs"], + "classes": [ + "modal-container", + "modal-wrapper", + "modal-mask", + "dialogs" + ], "radiusToken": "--border-radius-large", "states": [ { @@ -957,24 +1202,32 @@ "label": "its button row" } ], + "versionNotes": [ + { + "versions": [ + 32 + ], + "text": "uses the large and fully rounded corner radius; from 33 the element corner." + } + ], "tokens": [ { - "name": "--border-radius-large", + "name": "--nldesign-component-dialog-border-radius", "paints": "Corner", "callout": 1 }, { - "name": "--color-border", + "name": "--nldesign-component-dialog-border-color", "paints": "The rule under the header", "callout": 1 }, { - "name": "--animation-slow", + "name": "--nldesign-component-dialog-transition-duration", "paints": "Its open and close transition", "callout": 1 }, { - "name": "--color-primary-element", + "name": "--nldesign-component-dialog-confirm-background-color", "paints": "Its confirm button", "callout": 2 } @@ -994,7 +1247,9 @@ "title": "Progress & loading", "subtitle": "NcProgressBar and NcLoadingIcon", "ground": "content", - "classes": ["icon-loading"], + "classes": [ + "icon-loading" + ], "radiusToken": "--border-radius-pill", "states": [ { @@ -1008,24 +1263,40 @@ "label": "the spinner" } ], + "versionNotes": [ + { + "versions": [ + 32 + ], + "text": "the label is drawn in the primary element colour." + }, + { + "versions": [ + 33, + 34, + 35 + ], + "text": "the label is small text in the normal colour." + } + ], "tokens": [ { - "name": "--color-primary-element", + "name": "--nldesign-component-progress-background-color", "paints": "The filled part of the bar", "callout": 1 }, { - "name": "--color-background-darker", + "name": "--nldesign-component-progress-track-background-color", "paints": "Its track", "callout": 1 }, { - "name": "--border-radius-pill", + "name": "--nldesign-component-progress-border-radius", "paints": "Bar shape", "callout": 1 }, { - "name": "--animation-slow", + "name": "--nldesign-component-progress-transition-duration", "paints": "The spinner's rotation", "callout": 2 } @@ -1069,34 +1340,42 @@ "fixed": true } ], + "versionNotes": [ + { + "versions": [ + 32 + ], + "text": "labels can also take the light and lighter text colours; from 33 they do not." + } + ], "tokens": [ { - "name": "--color-primary-element", + "name": "--nldesign-component-button-primary-action-background-color", "paints": "Background", "callout": 1 }, { - "name": "--color-primary-element-text", + "name": "--nldesign-component-button-primary-action-color", "paints": "Label", "callout": 1 }, { - "name": "--color-primary-element-hover", + "name": "--nldesign-component-button-primary-action-hover-background-color", "paints": "Background on hover", "callout": 2 }, { - "name": "--color-main-text", + "name": "--nldesign-component-button-focus-border-color", "paints": "Focus ring", "callout": 3 }, { - "name": "--border-radius-element", + "name": "--nldesign-component-button-border-radius", "paints": "Corner", "callout": 1 }, { - "name": "--font-weight-element", + "name": "--nldesign-component-button-font-weight", "paints": "Label weight", "callout": 1 } @@ -1136,24 +1415,32 @@ "label": "background on hover" } ], + "versionNotes": [ + { + "versions": [ + 32 + ], + "text": "labels can also take the light and lighter text colours; from 33 they do not." + } + ], "tokens": [ { - "name": "--color-background-dark", + "name": "--nldesign-component-button-secondary-action-background-color", "paints": "Background", "callout": 1 }, { - "name": "--color-main-text", + "name": "--nldesign-component-button-secondary-action-color", "paints": "Label", "callout": 1 }, { - "name": "--color-background-darker", + "name": "--nldesign-component-button-secondary-action-hover-background-color", "paints": "Background on hover", "callout": 2 }, { - "name": "--border-radius-element", + "name": "--nldesign-component-button-secondary-action-border-radius", "paints": "Corner", "callout": 1 } @@ -1166,7 +1453,11 @@ "title": "Tertiary button", "subtitle": "NcButton type=\"tertiary\"", "ground": "content", - "classes": ["button-vue", "button-vue__wrapper", "button-vue__text"], + "classes": [ + "button-vue", + "button-vue__wrapper", + "button-vue__text" + ], "radiusToken": "--border-radius-element", "states": [ { @@ -1185,19 +1476,27 @@ "label": "background, pressed" } ], + "versionNotes": [ + { + "versions": [ + 32 + ], + "text": "labels can also take the light and lighter text colours; from 33 they do not." + } + ], "tokens": [ { - "name": "--color-primary-element-light-text", + "name": "--nldesign-component-button-color", "paints": "Label", "callout": 1 }, { - "name": "--color-primary-element-light", + "name": "--nldesign-component-button-hover-background-color", "paints": "Background on hover", "callout": 2 }, { - "name": "--color-primary-element-light-hover", + "name": "--nldesign-component-button-active-background-color", "paints": "Background when pressed", "callout": 3 } @@ -1210,7 +1509,11 @@ "title": "Error button", "subtitle": "NcButton type=\"error\"", "ground": "content", - "classes": ["button-vue", "button-vue__wrapper", "button-vue__text"], + "classes": [ + "button-vue", + "button-vue__wrapper", + "button-vue__text" + ], "radiusToken": "--border-radius-element", "states": [ { @@ -1224,20 +1527,34 @@ "label": "background on hover" } ], + "versionNotes": [ + { + "versions": [ + 32 + ], + "text": "labels can also take the light and lighter text colours; from 33 they do not." + } + ], "tokens": [ { - "name": "--color-error", + "name": "--nldesign-component-button-error-background-color", "paints": "Background", "callout": 1 }, { - "name": "--color-error-rgb", - "paints": "Its tinted fill, mixed from the same colour", + "name": "--nldesign-component-button-error-color", + "paints": "Label", "callout": 1 }, { - "name": "--color-error-hover", - "paints": "Background on hover", + "name": "--nldesign-component-button-error-background-color-rgb", + "paints": "Its tinted fill, mixed from the same colour", + "callout": 1, + "until": 32 + }, + { + "name": "--nldesign-component-button-error-hover-background-color", + "paints": "Background on hover, and its edge", "callout": 2 } ], @@ -1249,25 +1566,47 @@ "title": "Success button", "subtitle": "NcButton type=\"success\"", "ground": "content", - "classes": ["button-vue", "button-vue__wrapper", "button-vue__text"], + "classes": [ + "button-vue", + "button-vue__wrapper", + "button-vue__text" + ], "radiusToken": "--border-radius-element", "states": [ { "n": 1, "id": "default", "label": "background · icon" + }, + { + "n": 2, + "id": "hover", + "label": "hover" + } + ], + "versionNotes": [ + { + "versions": [ + 32 + ], + "text": "labels can also take the light and lighter text colours; from 33 they do not." } ], "tokens": [ { - "name": "--color-success", + "name": "--nldesign-component-button-success-background-color", "paints": "Background", "callout": 1 }, { - "name": "--color-element-success", - "paints": "Its icon", + "name": "--nldesign-component-button-success-color", + "paints": "Label", "callout": 1 + }, + { + "name": "--nldesign-component-button-success-hover-background-color", + "paints": "Background on hover, and its edge", + "callout": 2 } ], "fixed": [] @@ -1279,8 +1618,11 @@ "subtitle": "NcNoteCard, all four types", "ground": "content", "layout": "wide", - "classes": ["notecard"], + "classes": [ + "notecard" + ], "radiusToken": "--border-radius", + "versioned": true, "states": [ { "n": 1, @@ -1303,79 +1645,115 @@ "label": "success" } ], + "versionNotes": [ + { + "versions": [ + 32 + ], + "text": "the card is filled with a 10% tint mixed from the fill (Nextcloud 32) colour, the type colour is the stripe, and the corner is the normal radius." + }, + { + "versions": [ + 33, + 34, + 35 + ], + "text": "the type colour is the card fill, and the corner is the small radius." + } + ], "tokens": [ { - "name": "--color-info", - "paints": "Info stripe", + "name": "--nldesign-component-notecard-info-color", + "paints": "Info card fill (the stripe on Nextcloud 32)", "callout": 1 }, { - "name": "--color-element-info", - "paints": "Info glyph", + "name": "--nldesign-component-notecard-info-text-color", + "paints": "Info card text; white or black against its background when unset", "callout": 1 }, { - "name": "--border-radius", - "paints": "Card corner", + "name": "--nldesign-component-notecard-info-icon-color", + "paints": "Info glyph and side stripe", "callout": 1 }, { - "name": "--color-warning", - "paints": "Warning stripe", + "name": "--nldesign-component-notecard-info-color-rgb", + "paints": "Info fill on Nextcloud 32, mixed at 10% from this colour", + "callout": 1, + "until": 32 + }, + { + "name": "--nldesign-component-notecard-border-radius", + "paints": "Card corner", + "callout": 0 + }, + { + "name": "--nldesign-component-notecard-warning-color", + "paints": "Warning card fill (the stripe on Nextcloud 32)", "callout": 2 }, { - "name": "--color-element-warning", - "paints": "Warning glyph", + "name": "--nldesign-component-notecard-warning-text-color", + "paints": "Warning card text; white or black against its background when unset", "callout": 2 }, { - "name": "--color-warning-rgb", - "paints": "Warning fill, mixed from the same colour", + "name": "--nldesign-component-notecard-warning-icon-color", + "paints": "Warning glyph and side stripe", "callout": 2 }, { - "name": "--color-error", - "paints": "Error stripe", - "callout": 3 + "name": "--nldesign-component-notecard-warning-color-rgb", + "paints": "Warning fill on Nextcloud 32, mixed at 10% from this colour", + "callout": 2, + "until": 32 }, { - "name": "--color-element-error", - "paints": "Error glyph", + "name": "--nldesign-component-notecard-error-color", + "paints": "Error card fill (the stripe on Nextcloud 32)", "callout": 3 }, { - "name": "--color-border-error", - "paints": "Error card border", + "name": "--nldesign-component-notecard-error-text-color", + "paints": "Error card text; white or black against its background when unset", "callout": 3 }, { - "name": "--color-error-rgb", - "paints": "Error fill, mixed from the same colour", + "name": "--nldesign-component-notecard-error-icon-color", + "paints": "Error glyph and side stripe", "callout": 3 }, { - "name": "--color-success", - "paints": "Success stripe", - "callout": 4 + "name": "--nldesign-component-notecard-error-color-rgb", + "paints": "Error fill on Nextcloud 32, mixed at 10% from this colour", + "callout": 3, + "until": 32 }, { - "name": "--color-element-success", - "paints": "Success glyph", + "name": "--nldesign-component-notecard-success-color", + "paints": "Success card fill (the stripe on Nextcloud 32)", "callout": 4 }, { - "name": "--color-border-success", - "paints": "Success card border", + "name": "--nldesign-component-notecard-success-text-color", + "paints": "Success card text; white or black against its background when unset", "callout": 4 }, { - "name": "--color-success-rgb", - "paints": "Success fill, mixed from the same colour", + "name": "--nldesign-component-notecard-success-icon-color", + "paints": "Success glyph and side stripe", "callout": 4 + }, + { + "name": "--nldesign-component-notecard-success-color-rgb", + "paints": "Success fill on Nextcloud 32, mixed at 10% from this colour", + "callout": 4, + "until": 32 } ], - "fixed": [] + "fixed": [], + "separateStates": true }, { "id": "badge-counter", @@ -1397,24 +1775,32 @@ "label": "favourite star" } ], + "versionNotes": [ + { + "versions": [ + 32 + ], + "text": "the counter has fully rounded (pill) corners; from 33 it does not." + } + ], "tokens": [ { - "name": "--color-primary-element", + "name": "--nldesign-component-badge-background-color", "paints": "The highlighted bubble", "callout": 1 }, { - "name": "--color-primary-element-text", + "name": "--nldesign-component-badge-color", "paints": "Its number", "callout": 1 }, { - "name": "--border-radius-pill", + "name": "--nldesign-component-badge-border-radius", "paints": "Bubble shape", "callout": 1 }, { - "name": "--color-favorite", + "name": "--nldesign-component-badge-favorite-color", "paints": "The favourite star", "callout": 2 } @@ -1428,7 +1814,9 @@ "subtitle": "OCP.Toast, all four types", "ground": "content", "layout": "wide", - "classes": ["toastify"], + "classes": [ + "toastify" + ], "radiusToken": "--border-radius", "states": [ { @@ -1454,32 +1842,53 @@ ], "tokens": [ { - "name": "--color-success", - "paints": "Success toast", + "name": "--nldesign-component-toast-success-background-color", + "paints": "Success toast background", "callout": 1 }, { - "name": "--border-radius", - "paints": "Corner", + "name": "--nldesign-component-toast-success-color", + "paints": "Success toast text; white or black against its background when unset", "callout": 1 }, { - "name": "--color-error", - "paints": "Error toast", + "name": "--nldesign-component-toast-border-radius", + "paints": "Corner", + "callout": 0 + }, + { + "name": "--nldesign-component-toast-error-background-color", + "paints": "Error toast background", "callout": 2 }, { - "name": "--color-warning", - "paints": "Warning toast", + "name": "--nldesign-component-toast-error-color", + "paints": "Error toast text; white or black against its background when unset", + "callout": 2 + }, + { + "name": "--nldesign-component-toast-warning-background-color", + "paints": "Warning toast background", "callout": 3 }, { - "name": "--color-info", - "paints": "Info toast", + "name": "--nldesign-component-toast-warning-color", + "paints": "Warning toast text; white or black against its background when unset", + "callout": 3 + }, + { + "name": "--nldesign-component-toast-info-background-color", + "paints": "Info toast background", + "callout": 4 + }, + { + "name": "--nldesign-component-toast-info-color", + "paints": "Info toast text; white or black against its background when unset", "callout": 4 } ], - "fixed": [] + "fixed": [], + "separateStates": true }, { "id": "heading", @@ -1498,17 +1907,17 @@ ], "tokens": [ { - "name": "--font-face", + "name": "--nldesign-component-heading-font-family", "paints": "Typeface", "callout": 1 }, { - "name": "--color-main-text", + "name": "--nldesign-component-heading-color", "paints": "Heading colour", "callout": 1 }, { - "name": "--font-weight-heading", + "name": "--nldesign-component-heading-font-weight", "paints": "Heading weight", "callout": 1 } @@ -1539,17 +1948,17 @@ ], "tokens": [ { - "name": "--color-main-text", + "name": "--nldesign-component-paragraph-color", "paints": "Body text", "callout": 1 }, { - "name": "--font-face", + "name": "--nldesign-component-paragraph-font-family", "paints": "Typeface", "callout": 1 }, { - "name": "--font-weight-default", + "name": "--nldesign-component-paragraph-font-weight", "paints": "Body weight", "callout": 1 } @@ -1578,12 +1987,12 @@ ], "tokens": [ { - "name": "--color-primary", + "name": "--nldesign-component-link-color", "paints": "Link colour", "callout": 1 }, { - "name": "--color-primary-hover", + "name": "--nldesign-component-link-hover-color", "paints": "Link on hover", "callout": 2 } @@ -1617,19 +2026,19 @@ ], "tokens": [ { - "name": "--color-text-maxcontrast", + "name": "--nldesign-component-text-muted-color", "paints": "Secondary text that still has to pass AA", "callout": 1 }, { - "name": "--color-text-light", + "name": "--nldesign-component-text-light-color", "paints": "Text one step lighter than body", - "callout": 2 + "callout": 1 }, { - "name": "--color-text-lighter", + "name": "--nldesign-component-text-lighter-color", "paints": "The lightest text Nextcloud uses", - "callout": 3 + "callout": 1 } ], "fixed": [] @@ -1661,17 +2070,17 @@ ], "tokens": [ { - "name": "--color-text-error", + "name": "--nldesign-component-text-error-color", "paints": "Error text", "callout": 1 }, { - "name": "--color-text-warning", + "name": "--nldesign-component-text-warning-color", "paints": "Warning text", "callout": 2 }, { - "name": "--color-text-success", + "name": "--nldesign-component-text-success-color", "paints": "Success text", "callout": 3 } diff --git a/l10n/be.js b/l10n/be.js index a0e0059b..4c70da54 100644 --- a/l10n/be.js +++ b/l10n/be.js @@ -357,7 +357,10 @@ OC.L10N.register( "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "nplurals=2; plural=(n != 1);" ) diff --git a/l10n/be.json b/l10n/be.json index d5c6d9a7..83e807e5 100644 --- a/l10n/be.json +++ b/l10n/be.json @@ -356,7 +356,10 @@ "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "plurals": {} } diff --git a/l10n/bg.js b/l10n/bg.js index 42a09e58..6be17136 100644 --- a/l10n/bg.js +++ b/l10n/bg.js @@ -357,7 +357,10 @@ OC.L10N.register( "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "nplurals=2; plural=(n != 1);" ) diff --git a/l10n/bg.json b/l10n/bg.json index 5af0b94c..31e89a03 100644 --- a/l10n/bg.json +++ b/l10n/bg.json @@ -356,7 +356,10 @@ "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "plurals": {} } diff --git a/l10n/bs.js b/l10n/bs.js index 900dc608..9f626be9 100644 --- a/l10n/bs.js +++ b/l10n/bs.js @@ -357,7 +357,10 @@ OC.L10N.register( "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "nplurals=2; plural=(n != 1);" ) diff --git a/l10n/bs.json b/l10n/bs.json index 3cbe5a5c..29031306 100644 --- a/l10n/bs.json +++ b/l10n/bs.json @@ -356,7 +356,10 @@ "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "plurals": {} } diff --git a/l10n/ca.js b/l10n/ca.js index 67604e4b..a9b3c351 100644 --- a/l10n/ca.js +++ b/l10n/ca.js @@ -357,7 +357,10 @@ OC.L10N.register( "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "nplurals=2; plural=(n != 1);" ) diff --git a/l10n/ca.json b/l10n/ca.json index f993cc4a..a36e6656 100644 --- a/l10n/ca.json +++ b/l10n/ca.json @@ -356,7 +356,10 @@ "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "plurals": {} } diff --git a/l10n/cs.js b/l10n/cs.js index 76090022..4b6d4c1e 100644 --- a/l10n/cs.js +++ b/l10n/cs.js @@ -357,7 +357,10 @@ OC.L10N.register( "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "nplurals=2; plural=(n != 1);" ) diff --git a/l10n/cs.json b/l10n/cs.json index 65668085..e7b3bdd7 100644 --- a/l10n/cs.json +++ b/l10n/cs.json @@ -356,7 +356,10 @@ "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "plurals": {} } diff --git a/l10n/da.js b/l10n/da.js index 98172251..cc66de2d 100644 --- a/l10n/da.js +++ b/l10n/da.js @@ -357,7 +357,10 @@ OC.L10N.register( "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "nplurals=2; plural=(n != 1);" ) diff --git a/l10n/da.json b/l10n/da.json index 5d0116ac..d00f1a47 100644 --- a/l10n/da.json +++ b/l10n/da.json @@ -356,7 +356,10 @@ "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "plurals": {} } diff --git a/l10n/de.js b/l10n/de.js index 23f2b42c..c7bab4ff 100644 --- a/l10n/de.js +++ b/l10n/de.js @@ -357,7 +357,10 @@ OC.L10N.register( "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "nplurals=2; plural=(n != 1);" ) diff --git a/l10n/de.json b/l10n/de.json index 1c26a1da..fbbadb11 100644 --- a/l10n/de.json +++ b/l10n/de.json @@ -356,7 +356,10 @@ "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "plurals": {} } diff --git a/l10n/el.js b/l10n/el.js index 337b88cc..de4ab318 100644 --- a/l10n/el.js +++ b/l10n/el.js @@ -357,7 +357,10 @@ OC.L10N.register( "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "nplurals=2; plural=(n != 1);" ) diff --git a/l10n/el.json b/l10n/el.json index e2f8091d..edc18971 100644 --- a/l10n/el.json +++ b/l10n/el.json @@ -356,7 +356,10 @@ "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "plurals": {} } diff --git a/l10n/en.js b/l10n/en.js index bcecd569..5601be38 100644 --- a/l10n/en.js +++ b/l10n/en.js @@ -357,7 +357,10 @@ OC.L10N.register( "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "nplurals=2; plural=(n != 1);" ) diff --git a/l10n/en.json b/l10n/en.json index 5ab80e58..6c1fa53b 100644 --- a/l10n/en.json +++ b/l10n/en.json @@ -356,7 +356,46 @@ "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.", + "A Nextcloud base colour, read far beyond any one component. Tick \"Also edit Nextcloud's base tokens\" to change it.": "A Nextcloud base colour, read far beyond any one component. Tick \"Also edit Nextcloud's base tokens\" to change it.", + "Also edit Nextcloud's base tokens": "Also edit Nextcloud's base tokens", + "Describe the change in a few lines…": "Describe the change in a few lines…", + "Do not ask again": "Do not ask again", + "Don't ask before saving while a token set is active": "Don't ask before saving while a token set is active", + "Don't ask before saving while the stock Nextcloud theme is active": "Don't ask before saving while the stock Nextcloud theme is active", + "e.g. j.jansen": "e.g. j.jansen", + "Edit Nextcloud's base tokens?": "Edit Nextcloud's base tokens?", + "Edit them anyway": "Edit them anyway", + "Favicon": "Favicon", + "Give the new theme a name.": "Give the new theme a name.", + "Keep these changes as a new theme?": "Keep these changes as a new theme?", + "My organisation": "My organisation", + "Name for the new theme": "Name for the new theme", + "Navigation bar logo": "Navigation bar logo", + "Nextcloud {version}:": "Nextcloud {version}:", + "No, change the Nextcloud theme": "No, change the Nextcloud theme", + "On Nextcloud 32 and 33 the app icons are images, so \"Header glyphs\" does not recolour them: they stay white. The other header glyphs do follow it. From Nextcloud 34 the app menu follows it too.": "On Nextcloud 32 and 33 the app icons are images, so \"Header glyphs\" does not recolour them: they stay white. The other header glyphs do follow it. From Nextcloud 34 the app menu follows it too.", + "Removed (plain colour)": "Removed (plain colour)", + "Reset theme to Nextcloud": "Reset theme to Nextcloud", + "Save these overrides?": "Save these overrides?", + "The new theme could not be created (HTTP {status}).": "The new theme could not be created (HTTP {status}).", + "The new theme could not be created.": "The new theme could not be created.", + "The preview keeps the styles of Nextcloud {running}, the version this instance runs.": "The preview keeps the styles of Nextcloud {running}, the version this instance runs.", + "The theme could not be reset.": "The theme could not be reset.", + "The theme exporter is not loaded on this page, so no new theme was created.": "The theme exporter is not loaded on this page, so no new theme was created.", + "Theme \"{name}\" created and applied.": "Theme \"{name}\" created and applied.", + "Theme \"{name}\" created and set as the active theme. Reload the page to see it.": "Theme \"{name}\" created and set as the active theme. Reload the page to see it.", + "Theme \"{name}\" was created but could not be applied. Pick it in the theme dropdown.": "Theme \"{name}\" was created but could not be applied. Pick it in the theme dropdown.", + "These are the colours and sizes Nextcloud itself is built from, such as the main text colour. Changing one changes every part of Nextcloud that reads it, which is far more than the component you are looking at, and not only what this panel shows. To change one component, use its own rows instead.": "These are the colours and sizes Nextcloud itself is built from, such as the main text colour. Changing one changes every part of Nextcloud that reads it, which is far more than the component you are looking at, and not only what this panel shows. To change one component, use its own rows instead.", + "These values could not be written into a theme and are kept as overrides instead: {names}": "These values could not be written into a theme and are kept as overrides instead: {names}", + "They are applied on top of {set}.": "They are applied on top of {set}.", + "This fully resets the theme to stock Nextcloud: the overrides of the current theme and every change written over the Nextcloud theme are deleted, and the Nextcloud theming colours and logo return to their defaults. Custom token sets, fonts and custom CSS are kept. This cannot be undone.": "This fully resets the theme to stock Nextcloud: the overrides of the current theme and every change written over the Nextcloud theme are deleted, and the Nextcloud theming colours and logo return to their defaults. Custom token sets, fonts and custom CSS are kept. This cannot be undone.", + "Token editor": "Token editor", + "Yes, save as a new theme": "Yes, save as a new theme", + "You are working on the stock Nextcloud theme. Save the changes as a token set of their own, or write them over the Nextcloud theme itself. Writing over it changes Nextcloud for everyone; \"Reset theme to Nextcloud\" brings the original back.": "You are working on the stock Nextcloud theme. Save the changes as a token set of their own, or write them over the Nextcloud theme itself. Writing over it changes Nextcloud for everyone; \"Reset theme to Nextcloud\" brings the original back." }, "plurals": {} } diff --git a/l10n/es.js b/l10n/es.js index ed872d04..ad6e419d 100644 --- a/l10n/es.js +++ b/l10n/es.js @@ -357,7 +357,10 @@ OC.L10N.register( "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "nplurals=2; plural=(n != 1);" ) diff --git a/l10n/es.json b/l10n/es.json index 7127c888..dcc0de2c 100644 --- a/l10n/es.json +++ b/l10n/es.json @@ -356,7 +356,10 @@ "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "plurals": {} } diff --git a/l10n/et.js b/l10n/et.js index ebd52d8d..b72402b8 100644 --- a/l10n/et.js +++ b/l10n/et.js @@ -357,7 +357,10 @@ OC.L10N.register( "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "nplurals=2; plural=(n != 1);" ) diff --git a/l10n/et.json b/l10n/et.json index 850e09a2..e15f4ef9 100644 --- a/l10n/et.json +++ b/l10n/et.json @@ -356,7 +356,10 @@ "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "plurals": {} } diff --git a/l10n/fi.js b/l10n/fi.js index 19230389..eacab9f3 100644 --- a/l10n/fi.js +++ b/l10n/fi.js @@ -357,7 +357,10 @@ OC.L10N.register( "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "nplurals=2; plural=(n != 1);" ) diff --git a/l10n/fi.json b/l10n/fi.json index 7efd765e..16d6f803 100644 --- a/l10n/fi.json +++ b/l10n/fi.json @@ -356,7 +356,10 @@ "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "plurals": {} } diff --git a/l10n/fr.js b/l10n/fr.js index 3eec4bba..94282ed2 100644 --- a/l10n/fr.js +++ b/l10n/fr.js @@ -357,7 +357,10 @@ OC.L10N.register( "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "nplurals=2; plural=(n != 1);" ) diff --git a/l10n/fr.json b/l10n/fr.json index db94af33..8c8e09b5 100644 --- a/l10n/fr.json +++ b/l10n/fr.json @@ -356,7 +356,10 @@ "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "plurals": {} } diff --git a/l10n/ga.js b/l10n/ga.js index ba3f45a7..76246542 100644 --- a/l10n/ga.js +++ b/l10n/ga.js @@ -357,7 +357,10 @@ OC.L10N.register( "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "nplurals=2; plural=(n != 1);" ) diff --git a/l10n/ga.json b/l10n/ga.json index 304ae816..d2ffd0ac 100644 --- a/l10n/ga.json +++ b/l10n/ga.json @@ -356,7 +356,10 @@ "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "plurals": {} } diff --git a/l10n/hr.js b/l10n/hr.js index ac3124a9..a84a980b 100644 --- a/l10n/hr.js +++ b/l10n/hr.js @@ -357,7 +357,10 @@ OC.L10N.register( "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "nplurals=2; plural=(n != 1);" ) diff --git a/l10n/hr.json b/l10n/hr.json index 76544066..b46f4f35 100644 --- a/l10n/hr.json +++ b/l10n/hr.json @@ -356,7 +356,10 @@ "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "plurals": {} } diff --git a/l10n/hu.js b/l10n/hu.js index 6396a458..f9845b4b 100644 --- a/l10n/hu.js +++ b/l10n/hu.js @@ -357,7 +357,10 @@ OC.L10N.register( "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "nplurals=2; plural=(n != 1);" ) diff --git a/l10n/hu.json b/l10n/hu.json index eb08e8f8..6ebd8978 100644 --- a/l10n/hu.json +++ b/l10n/hu.json @@ -356,7 +356,10 @@ "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "plurals": {} } diff --git a/l10n/is.js b/l10n/is.js index a85ff730..2a66205c 100644 --- a/l10n/is.js +++ b/l10n/is.js @@ -357,7 +357,10 @@ OC.L10N.register( "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "nplurals=2; plural=(n != 1);" ) diff --git a/l10n/is.json b/l10n/is.json index 04128c27..9bd40117 100644 --- a/l10n/is.json +++ b/l10n/is.json @@ -356,7 +356,10 @@ "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "plurals": {} } diff --git a/l10n/it.js b/l10n/it.js index 82215ce7..86b40291 100644 --- a/l10n/it.js +++ b/l10n/it.js @@ -357,7 +357,10 @@ OC.L10N.register( "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "nplurals=2; plural=(n != 1);" ) diff --git a/l10n/it.json b/l10n/it.json index d9f9965a..59efa1c7 100644 --- a/l10n/it.json +++ b/l10n/it.json @@ -356,7 +356,10 @@ "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "plurals": {} } diff --git a/l10n/lb.js b/l10n/lb.js index 4a99d73f..d819bfee 100644 --- a/l10n/lb.js +++ b/l10n/lb.js @@ -357,7 +357,10 @@ OC.L10N.register( "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "nplurals=2; plural=(n != 1);" ) diff --git a/l10n/lb.json b/l10n/lb.json index 7af80aa0..6e544398 100644 --- a/l10n/lb.json +++ b/l10n/lb.json @@ -356,7 +356,10 @@ "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "plurals": {} } diff --git a/l10n/lt.js b/l10n/lt.js index 9e479da8..5cb121b1 100644 --- a/l10n/lt.js +++ b/l10n/lt.js @@ -357,7 +357,10 @@ OC.L10N.register( "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "nplurals=2; plural=(n != 1);" ) diff --git a/l10n/lt.json b/l10n/lt.json index 7ec03609..e6bd2300 100644 --- a/l10n/lt.json +++ b/l10n/lt.json @@ -356,7 +356,10 @@ "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "plurals": {} } diff --git a/l10n/lv.js b/l10n/lv.js index b0faea25..bb905928 100644 --- a/l10n/lv.js +++ b/l10n/lv.js @@ -357,7 +357,10 @@ OC.L10N.register( "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "nplurals=2; plural=(n != 1);" ) diff --git a/l10n/lv.json b/l10n/lv.json index 95af4e4a..d65271ca 100644 --- a/l10n/lv.json +++ b/l10n/lv.json @@ -356,7 +356,10 @@ "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "plurals": {} } diff --git a/l10n/mk.js b/l10n/mk.js index 0eb9ba72..73420f84 100644 --- a/l10n/mk.js +++ b/l10n/mk.js @@ -357,7 +357,10 @@ OC.L10N.register( "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "nplurals=2; plural=(n != 1);" ) diff --git a/l10n/mk.json b/l10n/mk.json index b4f53de9..38f11a67 100644 --- a/l10n/mk.json +++ b/l10n/mk.json @@ -356,7 +356,10 @@ "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "plurals": {} } diff --git a/l10n/mt.js b/l10n/mt.js index 2ca240ed..db9b9a64 100644 --- a/l10n/mt.js +++ b/l10n/mt.js @@ -357,7 +357,10 @@ OC.L10N.register( "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "nplurals=2; plural=(n != 1);" ) diff --git a/l10n/mt.json b/l10n/mt.json index 6c047f1d..d91b0199 100644 --- a/l10n/mt.json +++ b/l10n/mt.json @@ -356,7 +356,10 @@ "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "plurals": {} } diff --git a/l10n/nb.js b/l10n/nb.js index 9362800d..4b676b4b 100644 --- a/l10n/nb.js +++ b/l10n/nb.js @@ -357,7 +357,10 @@ OC.L10N.register( "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "nplurals=2; plural=(n != 1);" ) diff --git a/l10n/nb.json b/l10n/nb.json index c3e0fd25..1fb49c22 100644 --- a/l10n/nb.json +++ b/l10n/nb.json @@ -356,7 +356,10 @@ "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "plurals": {} } diff --git a/l10n/nl.js b/l10n/nl.js index 0b4a0456..759bec2c 100644 --- a/l10n/nl.js +++ b/l10n/nl.js @@ -357,7 +357,10 @@ OC.L10N.register( "Nothing": "Niets", "Changed": "Gewijzigd", "+%n more": "+nog %n", - "Apps, files, messages, and more": "Apps, bestanden, berichten en meer" + "Apps, files, messages, and more": "Apps, bestanden, berichten en meer", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "nplurals=2; plural=(n != 1);" ) diff --git a/l10n/nl.json b/l10n/nl.json index e6ebec7c..725b1ade 100644 --- a/l10n/nl.json +++ b/l10n/nl.json @@ -356,7 +356,10 @@ "Nothing": "Niets", "Changed": "Gewijzigd", "+%n more": "+nog %n", - "Apps, files, messages, and more": "Apps, bestanden, berichten en meer" + "Apps, files, messages, and more": "Apps, bestanden, berichten en meer", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "plurals": {} } diff --git a/l10n/pl.js b/l10n/pl.js index fe368b92..d36a86d3 100644 --- a/l10n/pl.js +++ b/l10n/pl.js @@ -357,7 +357,10 @@ OC.L10N.register( "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "nplurals=2; plural=(n != 1);" ) diff --git a/l10n/pl.json b/l10n/pl.json index ab23c5eb..bcf3050a 100644 --- a/l10n/pl.json +++ b/l10n/pl.json @@ -356,7 +356,10 @@ "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "plurals": {} } diff --git a/l10n/pt.js b/l10n/pt.js index 7d23d4ec..9bedc1bd 100644 --- a/l10n/pt.js +++ b/l10n/pt.js @@ -357,7 +357,10 @@ OC.L10N.register( "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "nplurals=2; plural=(n != 1);" ) diff --git a/l10n/pt.json b/l10n/pt.json index f4c48245..0d271289 100644 --- a/l10n/pt.json +++ b/l10n/pt.json @@ -356,7 +356,10 @@ "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "plurals": {} } diff --git a/l10n/rm.js b/l10n/rm.js index 0551dc06..6de22df6 100644 --- a/l10n/rm.js +++ b/l10n/rm.js @@ -357,7 +357,10 @@ OC.L10N.register( "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "nplurals=2; plural=(n != 1);" ) diff --git a/l10n/rm.json b/l10n/rm.json index 88b457f1..1b93e6bd 100644 --- a/l10n/rm.json +++ b/l10n/rm.json @@ -356,7 +356,10 @@ "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "plurals": {} } diff --git a/l10n/ro.js b/l10n/ro.js index 7b13bb5d..141e7afa 100644 --- a/l10n/ro.js +++ b/l10n/ro.js @@ -357,7 +357,10 @@ OC.L10N.register( "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "nplurals=2; plural=(n != 1);" ) diff --git a/l10n/ro.json b/l10n/ro.json index c300263b..e33f3610 100644 --- a/l10n/ro.json +++ b/l10n/ro.json @@ -356,7 +356,10 @@ "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "plurals": {} } diff --git a/l10n/ru.js b/l10n/ru.js index f35a6eda..954a0cbf 100644 --- a/l10n/ru.js +++ b/l10n/ru.js @@ -357,7 +357,10 @@ OC.L10N.register( "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "nplurals=2; plural=(n != 1);" ) diff --git a/l10n/ru.json b/l10n/ru.json index c865621d..3704c5e9 100644 --- a/l10n/ru.json +++ b/l10n/ru.json @@ -356,7 +356,10 @@ "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "plurals": {} } diff --git a/l10n/sk.js b/l10n/sk.js index 6e6185e5..8bff230c 100644 --- a/l10n/sk.js +++ b/l10n/sk.js @@ -357,7 +357,10 @@ OC.L10N.register( "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "nplurals=2; plural=(n != 1);" ) diff --git a/l10n/sk.json b/l10n/sk.json index d4d1aa70..e5357082 100644 --- a/l10n/sk.json +++ b/l10n/sk.json @@ -356,7 +356,10 @@ "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "plurals": {} } diff --git a/l10n/sl.js b/l10n/sl.js index 3db7eea8..0c3e5ef0 100644 --- a/l10n/sl.js +++ b/l10n/sl.js @@ -357,7 +357,10 @@ OC.L10N.register( "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "nplurals=2; plural=(n != 1);" ) diff --git a/l10n/sl.json b/l10n/sl.json index 3b03e48b..7d12ce13 100644 --- a/l10n/sl.json +++ b/l10n/sl.json @@ -356,7 +356,10 @@ "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "plurals": {} } diff --git a/l10n/sq.js b/l10n/sq.js index 4098c64b..6feeb1e4 100644 --- a/l10n/sq.js +++ b/l10n/sq.js @@ -357,7 +357,10 @@ OC.L10N.register( "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "nplurals=2; plural=(n != 1);" ) diff --git a/l10n/sq.json b/l10n/sq.json index 4a3e2061..8f78491b 100644 --- a/l10n/sq.json +++ b/l10n/sq.json @@ -356,7 +356,10 @@ "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "plurals": {} } diff --git a/l10n/sr.js b/l10n/sr.js index d7b7d04a..af16313f 100644 --- a/l10n/sr.js +++ b/l10n/sr.js @@ -357,7 +357,10 @@ OC.L10N.register( "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "nplurals=2; plural=(n != 1);" ) diff --git a/l10n/sr.json b/l10n/sr.json index d2032f19..d4dfca52 100644 --- a/l10n/sr.json +++ b/l10n/sr.json @@ -356,7 +356,10 @@ "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "plurals": {} } diff --git a/l10n/sv.js b/l10n/sv.js index 088d95f8..2e2a4e0a 100644 --- a/l10n/sv.js +++ b/l10n/sv.js @@ -357,7 +357,10 @@ OC.L10N.register( "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "nplurals=2; plural=(n != 1);" ) diff --git a/l10n/sv.json b/l10n/sv.json index 991d5450..e71294bd 100644 --- a/l10n/sv.json +++ b/l10n/sv.json @@ -356,7 +356,10 @@ "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "plurals": {} } diff --git a/l10n/tr.js b/l10n/tr.js index 59829bde..afcb0ff5 100644 --- a/l10n/tr.js +++ b/l10n/tr.js @@ -357,7 +357,10 @@ OC.L10N.register( "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "nplurals=2; plural=(n != 1);" ) diff --git a/l10n/tr.json b/l10n/tr.json index 70524af4..49534aa7 100644 --- a/l10n/tr.json +++ b/l10n/tr.json @@ -356,7 +356,10 @@ "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "plurals": {} } diff --git a/l10n/uk.js b/l10n/uk.js index 5c4d472f..31aade71 100644 --- a/l10n/uk.js +++ b/l10n/uk.js @@ -357,7 +357,10 @@ OC.L10N.register( "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "nplurals=2; plural=(n != 1);" ) diff --git a/l10n/uk.json b/l10n/uk.json index 0ed1d12d..8e2ec4b8 100644 --- a/l10n/uk.json +++ b/l10n/uk.json @@ -356,7 +356,10 @@ "Nothing": "Nothing", "Changed": "Changed", "+%n more": "+%n more", - "Apps, files, messages, and more": "Apps, files, messages, and more" + "Apps, files, messages, and more": "Apps, files, messages, and more", + "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.": "The primary colour drives this component. Switch off \"Let the primary colour drive every component\" to set it separately.", + "Let the primary colour drive every component": "Let the primary colour drive every component", + "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back.": "While this is on, the brand primary overrules any colour set on an individual component, and those controls are locked. Switching it off gives each component its own colour back." }, "plurals": {} } diff --git a/lib/Controller/CustomTokenSetController.php b/lib/Controller/CustomTokenSetController.php index e5e913cb..0237d71a 100644 --- a/lib/Controller/CustomTokenSetController.php +++ b/lib/Controller/CustomTokenSetController.php @@ -25,9 +25,11 @@ namespace OCA\Thematiq\Controller; use OCA\Thematiq\AppInfo\Application; +use OCA\Thematiq\Service\BrandingCaptureService; use OCA\Thematiq\Service\CssParserService; use OCA\Thematiq\Service\CustomTokenSetService; use OCA\Thematiq\Service\CustomTokenSetValidator; +use OCA\Thematiq\Service\DesignSystemService; use OCA\Thematiq\Service\ThemingAuditService; use OCA\Thematiq\Service\ThemingService; use OCA\Thematiq\Service\TokenSetConverterService; @@ -119,6 +121,22 @@ class CustomTokenSetController extends Controller { */ private ThemingService $themingService; + /** + * The shipped design-system manifest, used to allow-list a claimed id. + * + * @var DesignSystemService + */ + private DesignSystemService $designSystems; + + /** + * Copies Nextcloud's own branding into a theme saved from the editor. + * Optional so a caller that builds this controller by hand need not know + * about it; the container always injects it. + * + * @var BrandingCaptureService|null + */ + private ?BrandingCaptureService $brandingCapture; + /** * Constructor. * @@ -132,6 +150,8 @@ class CustomTokenSetController extends Controller { * @param IConfig $config The config service. * @param TokenSetConverterService $converter The theme converter, which runs before the validator. * @param ThemingService $themingService Core theming, for undoing a deleted set's sync. + * @param DesignSystemService $designSystems The design-system manifest, for allow-listing a claimed id. + * @param BrandingCaptureService|null $brandingCapture Copies Nextcloud's branding into a theme saved from the editor. * * @SuppressWarnings(PHPMD.ExcessiveParameterList) - Nextcloud's container injects through the constructor and nothing else; the * alternative is a service locator, which hides exactly these dependencies instead of removing any of them. @@ -147,6 +167,8 @@ public function __construct( IConfig $config, TokenSetConverterService $converter, ThemingService $themingService, + DesignSystemService $designSystems, + ?BrandingCaptureService $brandingCapture = null, ) { parent::__construct(appName: $appName, request: $request); $this->service = $service; @@ -157,6 +179,8 @@ public function __construct( $this->config = $config; $this->converter = $converter; $this->themingService = $themingService; + $this->designSystems = $designSystems; + $this->brandingCapture = $brandingCapture; }//end __construct() /** @@ -216,6 +240,87 @@ public function upload(): JSONResponse { return $read; } + // ALREADY A TOKEN SET — store it as it arrived. + // + // The token editor's "save as a new theme" serialises the active set and + // the admin's overrides straight into the `css/tokens/*.css` shape, so + // there is nothing to convert. Running it through the converter anyway + // was actively wrong: the converter's job is to make an incomplete NLDS + // document whole, and it does that by filling every unmapped target from + // the mapping table's fallbacks. An admin who changed one colour got a + // theme carrying 121 component tokens of Rijkshuisstijl defaults they + // never chose. + // + // Uploads are untouched — a file an admin picks off disk is still an + // unknown document and still goes through the converter. + if ($this->request->getParam('raw', false) === true) { + return $this->storeRaw(name: $name, slug: $slug, content: $read['content']); + } + + return $this->storeConverted(name: $name, slug: $slug, read: $read); + }//end upload() + + /** + * Store a token set that arrived already in the `css/tokens/*.css` shape. + * + * @param string $name The set's display name. + * @param string $slug The slug derived from the name. + * @param string $content The token set CSS, as sent. + * + * @return JSONResponse The persisted set, or the validator's error. + * + * @spec openspec/changes/nlds-theme-converter/specs/custom-token-sets/spec.md + */ + private function storeRaw(string $name, string $slug, string $content): JSONResponse { + $parsed = $this->mapFromCss(content: $content, slug: $slug); + if ($parsed instanceof JSONResponse) { + return $parsed; + } + + $parsed['css'] = $content; + + // Which design system the editor was looking at when it serialised + // this. Allow-listed against the shipped manifest rather than taken + // on trust: it decides which stylesheet layers every page load emits. + $claimed = trim((string)$this->request->getParam('designSystem', '')); + if ($claimed !== '' && isset($this->designSystems->getDesignSystems()[$claimed]) === true) { + $parsed['designSystem'] = $claimed; + } + + $response = $this->persist(name: $name, parsed: $parsed); + + // A theme saved from the editor keeps the Nextcloud branding that + // was on when it was saved — colours, background, logos, favicon — + // so applying it later brings that back too. Only once the set is + // safely stored: a refused save must not leave copied images behind. + if ($this->request->getParam('captureTheming', false) === true + && $this->brandingCapture !== null + && $response->getStatus() === 200 + ) { + // The persist() response is always an array; getData() is typed + // array|object, so say which before indexing it. + $data = $response->getData(); + if (is_array($data) === true) { + $data['theming'] = $this->brandingCapture->capture(setId: (string)$data['id']); + $response->setData($data); + } + } + + return $response; + }//end storeRaw() + + /** + * Convert an uploaded design-system document and store the result. + * + * @param string $name The set's display name. + * @param string $slug The slug derived from the name. + * @param array{content: string, sourceName: string|null} $read The payload from readInput(). + * + * @return JSONResponse `{ id, imported, skipped, warnings, report, counts, inputKind }` or an error. + * + * @spec openspec/changes/nlds-theme-converter/specs/custom-token-sets/spec.md + */ + private function storeConverted(string $name, string $slug, array $read): JSONResponse { try { $converted = $this->converter->convert( content: $read['content'], @@ -296,7 +401,7 @@ public function upload(): JSONResponse { } return $this->persist(name: $name, parsed: $parsed); - }//end upload() + }//end storeConverted() /** * Read the import payload from either the file picker or the paste box. @@ -476,7 +581,8 @@ private function persist(string $name, array $parsed): JSONResponse { // block are what lands on disk rather than a flat re-serialise. css: ($parsed['css'] ?? null), theming: ($parsed['theming'] ?? []), - logoAsset: ($parsed['logoAsset'] ?? null) + logoAsset: ($parsed['logoAsset'] ?? null), + designSystem: ($parsed['designSystem'] ?? null) ); } catch (RuntimeException $e) { $code = $e->getCode(); @@ -594,6 +700,11 @@ public function delete(string $id): JSONResponse { return new JSONResponse(['error' => $this->l->t('Token set not found.')], 404); } + // The branding it captured, and the images copied for it, go with it. + if ($this->brandingCapture !== null) { + $this->brandingCapture->forget(setId: $id); + } + $contentHash = null; if ($servedCss !== null) { $contentHash = 'sha256:' . substr(hash(algo: 'sha256', data: $servedCss), 0, 12); @@ -609,7 +720,7 @@ public function delete(string $id): JSONResponse { $this->themingService->resetToDefaults(); // Nothing is synced any more, so nothing is remembered as synced. - foreach (['logo', 'background'] as $imageKey) { + foreach (['logo', 'logoheader', 'favicon', 'background'] as $imageKey) { $this->config->deleteAppValue( Application::APP_ID, SettingsController::SYNCED_IMAGE_PREFIX . $imageKey diff --git a/lib/Controller/OverridesController.php b/lib/Controller/OverridesController.php index b4b63e29..1f2771c1 100644 --- a/lib/Controller/OverridesController.php +++ b/lib/Controller/OverridesController.php @@ -27,21 +27,33 @@ namespace OCA\Thematiq\Controller; +use OCA\Thematiq\AppInfo\Application; +use OCA\Thematiq\Service\BrandingCaptureService; +use OCA\Thematiq\Service\CssInjectionService; use OCA\Thematiq\Service\CssParserService; use OCA\Thematiq\Service\CustomOverridesService; use OCA\Thematiq\Service\ThemingAuditService; +use OCA\Thematiq\Service\ThemingService; use OCA\Thematiq\Service\TokenRegistry; use OCA\Thematiq\Settings\Admin; use OCP\AppFramework\Controller; use OCP\AppFramework\Http\Attribute\AuthorizedAdminSetting; use OCP\AppFramework\Http\DataDownloadResponse; use OCP\AppFramework\Http\JSONResponse; +use OCP\IConfig; use OCP\IRequest; /** * Controller for managing custom CSS token overrides. * - * Handles CRUD, import, and export of custom-overrides.css. + * Handles CRUD, import, and export of the overrides files, and the reset to + * stock Nextcloud. + * + * Every endpoint takes an optional `tokenSet`: the set whose overrides file + * is meant. The admin page can be wearing a set other than the instance's + * active one (a preview, or a switch that has not reloaded), and the stock set + * keeps its edits in a file of its own — see CustomOverridesService. Without + * it, the instance's active set is meant. * * @spec openspec/changes/retrofit-2026-05-24-annotate-nldesign/tasks.md#task-7 * @spec openspec/changes/retrofit-2026-05-24-annotate-nldesign/tasks.md#task-8 @@ -50,9 +62,21 @@ * @spec openspec/changes/retrofit-2026-05-24-annotate-nldesign/tasks.md#task-11 * @spec openspec/changes/retrofit-2026-05-24-annotate-nldesign/tasks.md#task-12 * @spec openspec/changes/retrofit-2026-05-24-annotate-nldesign/tasks.md#task-13 + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - The reset to stock touches every part a theme + * is made of: the overrides files, the active set and core theming. Each dependency is one of them; + * a separate reset service would move the same coupling one class along. */ class OverridesController extends Controller { + /** + * The answer to a failed write. The exception text names the absolute file + * path, which is not for the browser. + * + * @var string + */ + private const WRITE_FAILED = 'The token overrides could not be saved. Check that the web server can write to the app\'s css/ directory.'; + /** * The custom overrides service. * @@ -74,6 +98,29 @@ class OverridesController extends Controller { */ private ThemingAuditService $auditService; + /** + * The app config, for the active token set. + * + * @var IConfig + */ + private IConfig $config; + + /** + * The theming service, for resetting core theming with the theme. + * + * @var ThemingService + */ + private ThemingService $themingService; + + /** + * Copies Nextcloud's own branding into the theme on save. Optional so a + * caller that builds this controller by hand need not know about it; the + * container always injects it. + * + * @var BrandingCaptureService|null + */ + private ?BrandingCaptureService $brandingCapture; + /** * Constructor. * @@ -82,6 +129,9 @@ class OverridesController extends Controller { * @param CustomOverridesService $overridesService The custom overrides service. * @param CssParserService $cssParser The CSS parser service. * @param ThemingAuditService $auditService The theming audit trail service. + * @param IConfig $config The app config. + * @param ThemingService $themingService The theming service. + * @param BrandingCaptureService|null $brandingCapture Copies Nextcloud's branding into the theme on save. */ public function __construct( string $appName, @@ -89,13 +139,33 @@ public function __construct( CustomOverridesService $overridesService, CssParserService $cssParser, ThemingAuditService $auditService, + IConfig $config, + ThemingService $themingService, + ?BrandingCaptureService $brandingCapture = null, ) { parent::__construct(appName: $appName, request: $request); $this->overridesService = $overridesService; $this->cssParser = $cssParser; $this->auditService = $auditService; + $this->config = $config; + $this->themingService = $themingService; + $this->brandingCapture = $brandingCapture; }//end __construct() + /** + * The token set a request names, or null for the instance's active set. + * + * @return string|null The `tokenSet` parameter, or null when absent or empty. + */ + private function requestedTokenSet(): ?string { + $tokenSet = $this->request->getParam('tokenSet', ''); + if (is_string($tokenSet) === false || $tokenSet === '') { + return null; + } + + return $tokenSet; + }//end requestedTokenSet() + /** * Get the current custom token overrides. * @@ -110,7 +180,7 @@ public function __construct( */ #[AuthorizedAdminSetting(Admin::class)] public function getOverrides(): JSONResponse { - $overrides = $this->overridesService->read(); + $overrides = $this->overridesService->read(tokenSet: $this->requestedTokenSet()); $registry = TokenRegistry::getTokens(); $tabs = TokenRegistry::getTabLabels(); @@ -127,7 +197,13 @@ public function getOverrides(): JSONResponse { * Write new custom token overrides to custom-overrides.css. * * Accepts a JSON body with an 'overrides' key containing token name => value pairs. - * Only tokens in the TokenRegistry are accepted; others are silently ignored. + * A save with any token outside the TokenRegistry, or with a value the writer + * would drop, is refused with 400 naming those tokens, and nothing is written. + * + * `reset: true` instead resets the theme to stock Nextcloud — see + * {@see self::resetToStock()}. It shares this endpoint rather than taking a + * route of its own because Nextcloud caches the route collection per host + * for an hour, so a new route 404s on every warm instance until then. * * @return JSONResponse Status and count of written tokens. * @@ -137,18 +213,37 @@ public function getOverrides(): JSONResponse { #[AuthorizedAdminSetting(Admin::class)] public function setOverrides(): JSONResponse { $params = $this->request->getParams(); + + if (($params['reset'] ?? false) === true) { + return $this->resetToStock(); + } + $overrides = $params['overrides'] ?? []; if (is_array($overrides) === false) { return new JSONResponse(['error' => 'overrides must be an object'], 400); } - $before = $this->overridesService->read(); + // Refuse the whole save when any token would be dropped, so the answer, + // the written count and the audit entry all describe what reached the file. + $rejected = $this->overridesService->findRejected(tokens: $overrides); + if (empty($rejected) === false) { + return new JSONResponse( + [ + 'error' => 'Some tokens were not saved: ' . implode(', ', array_keys($rejected)), + 'rejected' => $rejected, + ], + 400 + ); + } + + $tokenSet = $this->requestedTokenSet(); + $before = $this->overridesService->read(tokenSet: $tokenSet); try { - $this->overridesService->write(tokens: $overrides); - } catch (\RuntimeException $e) { - return new JSONResponse(['error' => $e->getMessage()], 500); + $this->overridesService->write(tokens: $overrides, tokenSet: $tokenSet); + } catch (\RuntimeException) { + return new JSONResponse(['error' => self::WRITE_FAILED], 500); } $this->auditService->log( @@ -159,9 +254,79 @@ public function setOverrides(): JSONResponse { ] ); - return new JSONResponse(['status' => 'ok', 'written' => count($overrides)]); + $response = ['status' => 'ok', 'written' => count($overrides)]; + + // Saving a theme keeps the Nextcloud branding that is on now — + // colours, background, logos, favicon — with it, so applying the + // theme later brings that back as well. Not for the stock set: its + // branding IS Nextcloud's own settings, and applying stock resets them. + $setId = ($tokenSet ?? $this->config->getAppValue(Application::APP_ID, 'token_set', CssInjectionService::STOCK_TOKEN_SET)); + if ($this->request->getParam('captureTheming', false) === true + && $this->brandingCapture !== null + && $setId !== CssInjectionService::STOCK_TOKEN_SET + ) { + $response['theming'] = $this->brandingCapture->capture(setId: $setId); + } + + return new JSONResponse($response); }//end setOverrides() + /** + * Reset the theme to stock Nextcloud. + * + * Empties the overrides of the set that was active and of the stock set, + * makes the stock set the active one, and undoes what the theming sync + * wrote into core theming. What is left is what the running Nextcloud + * paints on its own. Instance-wide features that do not belong to a theme + * — freeform custom CSS, custom fonts, the display toggles, group and + * per-app theming, and the custom token sets themselves — are kept. + * + * Both files are emptied, not only the active set's: the stock set is + * where this lands, and its own edits are exactly what "stock" excludes. + * + * @return JSONResponse `{status: "ok", tokenSet: "nextcloud"}`, or 500 when a file cannot be written. + * + * @spec openspec/specs/theming-audit/spec.md#requirement-complete-call-site-coverage + */ + private function resetToStock(): JSONResponse { + $stock = CssInjectionService::STOCK_TOKEN_SET; + $previous = $this->config->getAppValue(Application::APP_ID, 'token_set', $stock); + + $emptied = []; + foreach (array_unique([$previous, $stock]) as $tokenSet) { + $before = $this->overridesService->read(tokenSet: $tokenSet); + + try { + $this->overridesService->write(tokens: [], tokenSet: $tokenSet); + } catch (\RuntimeException) { + return new JSONResponse(['error' => self::WRITE_FAILED], 500); + } + + if ($before !== []) { + $emptied[] = $before; + } + } + + $this->config->setAppValue(Application::APP_ID, 'token_set', $stock); + $this->themingService->resetToDefaults(); + // Nothing is synced any more, so nothing is remembered as synced — the + // same keys SettingsController::resetThemingValues() clears. + foreach (['logo', 'background'] as $imageKey) { + $this->config->deleteAppValue(Application::APP_ID, SettingsController::SYNCED_IMAGE_PREFIX . $imageKey); + } + + // Recorded as what it did, in the actions the audit trail already knows. + foreach ($emptied as $before) { + $this->auditService->log(action: 'overrides_written', context: ['old' => $before, 'new' => []]); + } + + if ($previous !== $stock) { + $this->auditService->log(action: 'token_set_changed', context: ['old' => $previous, 'new' => $stock]); + } + + return new JSONResponse(['status' => 'ok', 'tokenSet' => $stock]); + }//end resetToStock() + /** * Download custom-overrides.css as a file. * @@ -171,7 +336,7 @@ public function setOverrides(): JSONResponse { */ #[AuthorizedAdminSetting(Admin::class)] public function exportOverrides(): DataDownloadResponse { - $content = $this->overridesService->getRawContent(); + $content = $this->overridesService->getRawContent(tokenSet: $this->requestedTokenSet()); return new DataDownloadResponse( data: $content, @@ -203,7 +368,8 @@ public function importOverrides(): JSONResponse { return new JSONResponse(['error' => 'Could not read uploaded file'], 400); } - $parsed = $this->cssParser->parseDeclarations($content); + // The light values only; an exported file also carries the dark blocks. + $parsed = $this->cssParser->parseOverridesFile(css: $content); if ($parsed === null) { return new JSONResponse( ['error' => 'No CSS custom property declarations found in the uploaded file'], @@ -297,9 +463,9 @@ private function writeImportedTokens(array $parsed, string $rawContent): JSONRes } try { - $this->overridesService->write(tokens: $toImport); - } catch (\RuntimeException $e) { - return new JSONResponse(['error' => $e->getMessage()], 500); + $this->overridesService->write(tokens: $toImport, tokenSet: $this->requestedTokenSet()); + } catch (\RuntimeException) { + return new JSONResponse(['error' => self::WRITE_FAILED], 500); } $this->auditService->log( diff --git a/lib/Controller/SettingsController.php b/lib/Controller/SettingsController.php index f7e3d839..90e0219b 100644 --- a/lib/Controller/SettingsController.php +++ b/lib/Controller/SettingsController.php @@ -323,6 +323,96 @@ public function setSloganSetting(bool $hideSlogan): JSONResponse { return new JSONResponse(['status' => 'ok', 'hideSlogan' => $hideSlogan]); }//end setSloganSetting() + /** + * Set whether the brand primary overrules the component tokens it used to drive. + * + * Turning this on emits `css/primary-lock.css`, which forces every component + * token flagged `primary` in `scripts/mapping/component-tokens.json` back to + * the brand value. Stored per-component values are left untouched, so turning + * it off restores them. + * + * @param bool $primaryDrivesComponents Whether the primary overrules component tokens. + * + * @return JSONResponse The response with the status. + * + * @spec openspec/specs/component-tokens/spec.md + * @spec openspec/specs/theming-audit/spec.md#requirement-complete-call-site-coverage + * + * @SuppressWarnings(PHPMD.LongVariable) - the parameter name IS the wire contract: Nextcloud binds it from the JSON body key the + * admin panel posts, so shortening it to satisfy a length rule would rename the API field. + */ + #[AuthorizedAdminSetting(Admin::class)] + public function setPrimaryDrivesComponentsSetting(bool $primaryDrivesComponents): JSONResponse { + $previous = ($this->config->getAppValue(Application::APP_ID, 'primary_drives_components', '0') === '1'); + $this->saveBooleanSetting(key: 'primary_drives_components', value: $primaryDrivesComponents); + + $this->auditService->log( + action: 'toggle_changed', + context: [ + 'key' => 'primary_drives_components', + 'old' => $previous, + 'new' => $primaryDrivesComponents, + ] + ); + + return new JSONResponse( + ['status' => 'ok', 'primaryDrivesComponents' => $primaryDrivesComponents] + ); + }//end setPrimaryDrivesComponentsSetting() + + /** + * Set whether Save overrides asks for confirmation first. + * + * Two flags, because the two questions are not the same one. On the stock + * `nextcloud` set there is a real choice to make — keep the edits as a new + * token set, or write them over the running Nextcloud theme — and on any + * other set there is only a confirmation. An admin who has stopped wanting + * one has not necessarily stopped wanting the other. + * + * Both default to ON. The dialogs offer "do not ask again", and the pair of + * controls under the token editor turns them back on, so the choice is never + * one-way. + * + * @param bool $confirmSaveStock Whether to ask when the stock set is active. + * @param bool $confirmSaveTheme Whether to ask when a token set is active. + * + * @return JSONResponse The response with the status. + * + * @spec openspec/specs/theming-audit/spec.md#requirement-complete-call-site-coverage + */ + #[AuthorizedAdminSetting(Admin::class)] + public function setSaveConfirmSettings(bool $confirmSaveStock, bool $confirmSaveTheme): JSONResponse { + foreach ( + [ + 'confirm_save_stock' => $confirmSaveStock, + 'confirm_save_theme' => $confirmSaveTheme, + ] as $key => $value + ) { + $previous = ($this->config->getAppValue(Application::APP_ID, $key, '1') === '1'); + if ($previous === $value) { + continue; + } + + $this->saveBooleanSetting(key: $key, value: $value); + $this->auditService->log( + action: 'toggle_changed', + context: [ + 'key' => $key, + 'old' => $previous, + 'new' => $value, + ] + ); + } + + return new JSONResponse( + [ + 'status' => 'ok', + 'confirmSaveStock' => $confirmSaveStock, + 'confirmSaveTheme' => $confirmSaveTheme, + ] + ); + }//end setSaveConfirmSettings() + /** * Set the show menu labels setting. * @@ -451,7 +541,7 @@ private function resetThemingValues(): JSONResponse { $reset = $this->themingService->resetToDefaults(); // Nothing is synced any more, so nothing is remembered as synced. - foreach (['logo', 'background'] as $imageKey) { + foreach (['logo', 'logoheader', 'favicon', 'background'] as $imageKey) { $this->config->deleteAppValue(Application::APP_ID, self::SYNCED_IMAGE_PREFIX . $imageKey); } @@ -498,7 +588,12 @@ private function buildThemingSnapshot(): array { 'logo_url' => $imgManager->getImageUrl('logo'), 'background_url' => $imgManager->getImageUrl('background'), 'has_custom_logo' => $imgManager->hasImage('logo'), + 'has_custom_logoheader' => $imgManager->hasImage('logoheader'), + 'has_custom_favicon' => $imgManager->hasImage('favicon'), 'has_custom_background' => $imgManager->hasImage('background'), + // 'backgroundColor' when "Remove background image" is on — the + // state a theme that captured the panel may need to put back. + 'background_mime' => $this->config->getAppValue('theming', 'backgroundMime', ''), // What a reset lands on, so the stock set's dialog can show it // before it is applied rather than guess. 'default_primary_color' => $defaults['primary_color'], @@ -515,6 +610,16 @@ private function buildThemingSnapshot(): array { self::SYNCED_IMAGE_PREFIX . 'background', '' ), + 'synced_logoheader' => $this->config->getAppValue( + Application::APP_ID, + self::SYNCED_IMAGE_PREFIX . 'logoheader', + '' + ), + 'synced_favicon' => $this->config->getAppValue( + Application::APP_ID, + self::SYNCED_IMAGE_PREFIX . 'favicon', + '' + ), ]; }//end buildThemingSnapshot() diff --git a/lib/Service/BrandingCaptureService.php b/lib/Service/BrandingCaptureService.php new file mode 100644 index 00000000..30246d97 --- /dev/null +++ b/lib/Service/BrandingCaptureService.php @@ -0,0 +1,318 @@ + + * @copyright 2026 Conduction B.V. + * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * @link https://github.com/ConductionNL/thematiq + */ + +declare(strict_types=1); + +namespace OCA\Thematiq\Service; + +use OCA\Thematiq\AppInfo\Application; +use OCA\Thematiq\Controller\SettingsController; +use OCA\Theming\ImageManager; +use OCP\App\IAppManager; +use OCP\IConfig; + +/** + * Keeps Nextcloud's own branding with a theme. + * + * Nextcloud's Theming panel ("Background and color") holds the primary + * colour, the background colour, whether the background image was removed, + * and the uploaded background, logo, navigation-bar logo and favicon. None of + * that lives in a token set, so a theme saved from the editor used to come + * back without it: applying the theme later re-coloured the components and + * left the branding of whatever theme happened to be on before. + * + * {@see capture()} copies that branding into the theme when it is saved, and + * the theming sync ({@see ThemingService}) puts it back when the theme is + * applied. Images are copied as files, because core takes an image only as + * a file on disk (`ImageManager::updateImage()`), and they are copied rather + * than referenced because the slot they came from is overwritten by the next + * theme that brings an image of its own. + * + * The captured block is kept in one app value for every set — shipped and + * custom alike — because a shipped set's metadata is a read-only file in the + * app, and one store for both keeps "what this theme remembers" in one place. + * {@see TokenSetService} lays it over the set's own theming. + * + * @spec openspec/specs/theming-sync/spec.md#requirement-theming-metadata-in-token-sets + */ +class BrandingCaptureService { + + /** + * The app value holding every set's captured branding, by set id. + */ + public const CAPTURED_KEY = 'captured_theming'; + + /** + * The image slots Nextcloud's Theming panel offers. + */ + public const IMAGE_KEYS = ['logo', 'logoheader', 'favicon', 'background']; + + /** + * The file extension for each image type core accepts. + */ + private const EXTENSIONS = [ + 'image/svg+xml' => 'svg', + 'image/svg' => 'svg', + 'image/png' => 'png', + 'image/jpeg' => 'jpg', + 'image/jpg' => 'jpg', + 'image/gif' => 'gif', + 'image/webp' => 'webp', + 'image/x-icon' => 'ico', + 'image/vnd.microsoft.icon' => 'ico', + ]; + + /** + * Core's theming image store. + * + * @var ImageManager + */ + private ImageManager $imageManager; + + /** + * The app config: core's theming values, and the captured store. + * + * @var IConfig + */ + private IConfig $config; + + /** + * The app manager, for the app's own image directories. + * + * @var IAppManager + */ + private IAppManager $appManager; + + /** + * Constructor. + * + * @param ImageManager $imageManager Core's theming image store. + * @param IConfig $config The app config. + * @param IAppManager $appManager The app manager. + */ + public function __construct( + ImageManager $imageManager, + IConfig $config, + IAppManager $appManager, + ) { + $this->imageManager = $imageManager; + $this->config = $config; + $this->appManager = $appManager; + }//end __construct() + + /** + * Copy Nextcloud's current branding into a theme, and remember it. + * + * The result is the theme's theming block from now on. `captured` marks it + * as a snapshot of a whole panel rather than a set's suggestion: applying + * it restores exactly this, including a background image that was + * removed, or the default one that was never replaced. + * + * The slots are then recorded as synced to the copied files, because they + * ARE those files right now — without it the next apply of this very theme + * would offer to "change" every image to itself. + * + * @param string $setId The token set the branding belongs to. + * + * @return array The captured theming block. + * + * @spec openspec/specs/theming-sync/spec.md#requirement-theming-metadata-in-token-sets + */ + public function capture(string $setId): array { + $this->deleteFiles(setId: $setId); + + $theming = ['captured' => true]; + + foreach (['primary_color', 'background_color'] as $colorKey) { + $value = $this->config->getAppValue('theming', $colorKey, ''); + if ($value !== '') { + $theming[$colorKey] = $value; + } + } + + // Three states, not two: an image of the admin's own, the image + // removed in favour of the plain colour, or Nextcloud's default image. + $backgroundMime = $this->config->getAppValue('theming', 'backgroundMime', ''); + $theming['background_mode'] = 'default'; + if ($backgroundMime === 'backgroundColor') { + $theming['background_mode'] = 'color'; + } + + foreach (self::IMAGE_KEYS as $imageKey) { + $path = $this->copyImage(setId: $setId, imageKey: $imageKey); + if ($path === null) { + continue; + } + + $theming[$imageKey] = $path; + if ($imageKey === 'background') { + $theming['background_mode'] = 'image'; + } + + $this->config->setAppValue(Application::APP_ID, SettingsController::SYNCED_IMAGE_PREFIX . $imageKey, $path); + } + + $captured = $this->all(); + $captured[$setId] = $theming; + $this->save(captured: $captured); + + return $theming; + }//end capture() + + /** + * Every set's captured branding, by set id. + * + * @return array> The captured blocks. + * + * @spec openspec/specs/theming-sync/spec.md#requirement-theming-metadata-in-token-sets + */ + public function all(): array { + $decoded = json_decode((string)$this->config->getAppValue(Application::APP_ID, self::CAPTURED_KEY, '{}'), true); + if (is_array($decoded) === false) { + return []; + } + + return $decoded; + }//end all() + + /** + * Forget a set's captured branding and remove its copied images. + * + * @param string $setId The token set. + * + * @return void + * + * @spec openspec/specs/theming-sync/spec.md#requirement-theming-metadata-in-token-sets + */ + public function forget(string $setId): void { + $this->deleteFiles(setId: $setId); + + $captured = $this->all(); + if (isset($captured[$setId]) === true) { + unset($captured[$setId]); + $this->save(captured: $captured); + } + }//end forget() + + /** + * Copy one of core's images into the app, for a set. + * + * Named after the set and the slot, with `-captured-` in between, so a + * copy can never overwrite a shipped municipality logo or a logo the + * theme converter extracted (`img/logos/{id}.{ext}`). + * + * @param string $setId The token set. + * @param string $imageKey The image slot. + * + * @return string|null The app-relative path written, or null when the slot holds no image of the admin's. + */ + private function copyImage(string $setId, string $imageKey): ?string { + if ($this->imageManager->hasImage($imageKey) === false) { + return null; + } + + $mime = $this->config->getAppValue('theming', $imageKey . 'Mime', ''); + $extension = (self::EXTENSIONS[$mime] ?? null); + if ($extension === null) { + return null; + } + + try { + $contents = $this->imageManager->getImage($imageKey)->getContent(); + } catch (\Throwable $e) { + // An unreadable slot is left out of the theme rather than failing + // the save it rides on: the tokens still saved, only this image + // will not come back with the theme. + return null; + } + + $directory = $this->directoryFor(imageKey: $imageKey); + $absolute = $this->appManager->getAppPath(Application::APP_ID) . '/' . $directory; + if (is_dir($absolute) === false && mkdir($absolute, 0755, true) === false && is_dir($absolute) === false) { + return null; + } + + $file = $this->fileName(setId: $setId, imageKey: $imageKey) . '.' . $extension; + if (file_put_contents($absolute . '/' . $file, $contents) === false) { + return null; + } + + return $directory . '/' . $file; + }//end copyImage() + + /** + * Remove every image copied for a set, whatever its extension. + * + * @param string $setId The token set. + * + * @return void + */ + private function deleteFiles(string $setId): void { + $appPath = $this->appManager->getAppPath(Application::APP_ID); + + foreach (self::IMAGE_KEYS as $imageKey) { + $base = $appPath . '/' . $this->directoryFor(imageKey: $imageKey) . '/' . $this->fileName(setId: $setId, imageKey: $imageKey); + foreach (array_unique(array_values(self::EXTENSIONS)) as $extension) { + if (is_file($base . '.' . $extension) === true) { + unlink($base . '.' . $extension); + } + } + } + }//end deleteFiles() + + /** + * The app directory an image slot is copied into: the two ThemingService + * accepts a sync from. + * + * @param string $imageKey The image slot. + * + * @return string The app-relative directory. + */ + private function directoryFor(string $imageKey): string { + if ($imageKey === 'background') { + return 'img/backgrounds'; + } + + return 'img/logos'; + }//end directoryFor() + + /** + * The file name (without extension) a set's copy of a slot is kept under. + * + * @param string $setId The token set. + * @param string $imageKey The image slot. + * + * @return string The file name. + */ + private function fileName(string $setId, string $imageKey): string { + return $setId . '-captured-' . $imageKey; + }//end fileName() + + /** + * Persist every set's captured branding. + * + * @param array> $captured The captured blocks, by set id. + * + * @return void + */ + private function save(array $captured): void { + $this->config->setAppValue( + Application::APP_ID, + self::CAPTURED_KEY, + json_encode($captured, JSON_UNESCAPED_SLASHES) + ); + }//end save() +}//end class diff --git a/lib/Service/ComplianceReportService.php b/lib/Service/ComplianceReportService.php index 6d929cdd..ecda2b37 100644 --- a/lib/Service/ComplianceReportService.php +++ b/lib/Service/ComplianceReportService.php @@ -481,7 +481,9 @@ private function evaluatePair( return $entry; } - $ratio = round($this->contrast->ratio(first: $fgColor, second: $bgColor), 2); + // Measured as it renders, so a translucent colour is blended first. Both + // values parsed above, so measure() cannot answer null here. + $ratio = round(($this->contrast->measure(foreground: (string)$fgResolved['value'], background: (string)$bgResolved['value']) ?? 0.0), 2); $entry['ratio'] = $ratio; $entry['verdict'] = 'fail'; diff --git a/lib/Service/ConfigBundleService.php b/lib/Service/ConfigBundleService.php index 977e6c95..0dad0abe 100644 --- a/lib/Service/ConfigBundleService.php +++ b/lib/Service/ConfigBundleService.php @@ -251,6 +251,7 @@ public function export(): array { 'tokenSet' => $this->config->getAppValue(Application::APP_ID, 'token_set', 'nextcloud'), 'hideSlogan' => ($this->config->getAppValue(Application::APP_ID, 'hide_slogan', '0') === '1'), 'showMenuLabels' => ($this->config->getAppValue(Application::APP_ID, 'show_menu_labels', '0') === '1'), + 'primaryDrivesComponents' => ($this->config->getAppValue(Application::APP_ID, 'primary_drives_components', '0') === '1'), 'disabledApps' => $this->appThemingService->getDisabledApps(), 'upstreamFreshnessEnabled' => $this->freshnessService->isEnabled(), ], @@ -440,6 +441,11 @@ private function validateConfigSection(array $bundle, array &$errors): array { $errors[] = ['section' => 'config', 'message' => '"config.showMenuLabels" must be a boolean.']; } + $primaryDrivesComponents = ($config['primaryDrivesComponents'] ?? false); + if (is_bool($primaryDrivesComponents) === false) { + $errors[] = ['section' => 'config', 'message' => '"config.primaryDrivesComponents" must be a boolean.']; + } + $disabledApps = $this->validateDisabledApps(config: $config, errors: $errors); $upstreamFreshnessEnabled = ($config['upstreamFreshnessEnabled'] ?? false); @@ -451,6 +457,7 @@ private function validateConfigSection(array $bundle, array &$errors): array { 'tokenSet' => $tokenSet, 'hideSlogan' => ($hideSlogan === true), 'showMenuLabels' => ($showMenuLabels === true), + 'primaryDrivesComponents' => ($primaryDrivesComponents === true), 'disabledApps' => $disabledApps, 'upstreamFreshnessEnabled' => ($upstreamFreshnessEnabled === true), ]; @@ -549,7 +556,7 @@ private function validateOverridesSection(array $bundle, array &$errors): array return ['tokens' => [], 'skipped' => []]; } - $parsed = $this->cssParser->parseDeclarations(content: $css); + $parsed = $this->cssParser->parseOverridesFile(css: $css); if ($parsed === null) { $parsed = []; } @@ -840,6 +847,7 @@ private function buildSectionSummary(array $resolved): array { 'tokenSet' => $resolved['config']['tokenSet'], 'hideSlogan' => $resolved['config']['hideSlogan'], 'showMenuLabels' => $resolved['config']['showMenuLabels'], + 'primaryDrivesComponents' => $resolved['config']['primaryDrivesComponents'], 'disabledAppsCount' => count($resolved['config']['disabledApps']), 'upstreamFreshnessEnabled' => $resolved['config']['upstreamFreshnessEnabled'], ], @@ -884,9 +892,15 @@ private function apply(array $resolved): void { $showMenuLabelsValue = '1'; } + $primaryDrivesComponentsValue = '0'; + if ($config['primaryDrivesComponents'] === true) { + $primaryDrivesComponentsValue = '1'; + } + $this->config->setAppValue(Application::APP_ID, 'token_set', $config['tokenSet']); $this->config->setAppValue(Application::APP_ID, 'hide_slogan', $hideSloganValue); $this->config->setAppValue(Application::APP_ID, 'show_menu_labels', $showMenuLabelsValue); + $this->config->setAppValue(Application::APP_ID, 'primary_drives_components', $primaryDrivesComponentsValue); $this->appThemingService->setDisabledApps(appIds: $config['disabledApps']); $this->freshnessService->setEnabled(enabled: $config['upstreamFreshnessEnabled']); diff --git a/lib/Service/ContrastService.php b/lib/Service/ContrastService.php index 341cf12d..d2077f5b 100644 --- a/lib/Service/ContrastService.php +++ b/lib/Service/ContrastService.php @@ -88,12 +88,17 @@ public function check(array $declarations): array { continue; } - $fgRgb = $this->parseColor(value: $fgValue); - $bgRgb = $this->parseColor(value: $bgValue); + // Translucent colours are measured as they render: the background + // over the page background, the foreground over that. + $ratio = $this->measure( + foreground: $fgValue, + background: $bgValue, + page: ($declarations['--nldesign-color-background'] ?? null) + ); $pairLabel = $pair['fg'] . ' vs ' . $pair['bg']; - if ($fgRgb === null || $bgRgb === null) { + if ($ratio === null) { $warnings[] = [ 'pair' => $pairLabel, 'ratio' => null, @@ -104,7 +109,6 @@ public function check(array $declarations): array { continue; } - $ratio = $this->ratio(first: $fgRgb, second: $bgRgb); if ($ratio < $pair['threshold']) { $warnings[] = [ 'pair' => $pairLabel, @@ -142,8 +146,6 @@ public function check(array $declarations): array { * @spec openspec/specs/app-token-set-selection/spec.md */ public function evaluate(array $candidates, string $background): array { - $backgroundRgb = $this->parseColor(value: $background); - $results = []; foreach ($candidates as $candidate) { $threshold = self::ROLE_UI_THRESHOLD; @@ -151,9 +153,10 @@ public function evaluate(array $candidates, string $background): array { $threshold = self::ROLE_TEXT_THRESHOLD; } - $candidateRgb = $this->parseColor(value: (string)$candidate['value']); + // A translucent candidate is measured blended over the background. + $measured = $this->measure(foreground: (string)$candidate['value'], background: $background); - if ($candidateRgb === null || $backgroundRgb === null) { + if ($measured === null) { $results[] = [ 'name' => $candidate['name'], 'ratio' => null, @@ -165,7 +168,7 @@ public function evaluate(array $candidates, string $background): array { continue; } - $ratio = round($this->ratio(first: $candidateRgb, second: $backgroundRgb), 2); + $ratio = round($measured, 2); $results[] = [ 'name' => $candidate['name'], @@ -229,9 +232,11 @@ private function relativeLuminance(array $rgb): float { /** * Parse a CSS colour literal into an [r, g, b] triple. * - * Supports #rgb, #rrggbb, rgb(r, g, b) and rgba(r, g, b, a). Any other - * value (var(), named colours, hsl(), gradients) returns null so the - * caller can mark the pair unevaluated. + * Supports #rgb, #rgba, #rrggbb, #rrggbbaa, rgb() and rgba(). The alpha is + * dropped here, so this answers "which colour", not "what renders"; a + * contrast measurement goes through {@see self::measure()}, which blends. + * Any other value (var(), named colours, hsl(), gradients) returns null so + * the caller can mark the pair unevaluated. * * @param string $value The raw CSS colour value. * @@ -240,29 +245,140 @@ private function relativeLuminance(array $rgb): float { * @spec openspec/changes/custom-token-set-upload/tasks.md#task-1.3 */ public function parseColor(string $value): ?array { + $rgba = $this->parseColorWithAlpha(value: $value); + if ($rgba === null) { + return null; + } + + return [$rgba[0], $rgba[1], $rgba[2]]; + }//end parseColor() + + /** + * Parse a CSS colour literal into an [r, g, b, a] quadruple. + * + * Supports #rgb, #rgba, #rrggbb, #rrggbbaa, rgb() and rgba() with a number + * or percentage alpha. Alpha is 1.0 for an opaque colour, rounded to three + * decimals. + * + * @param string $value The raw CSS colour value. + * + * @return array{0: int, 1: int, 2: int, 3: float}|null The parsed colour, or null. + * + * @spec openspec/changes/authoring-token-value-types/tasks.md#task-3.1 + */ + public function parseColorWithAlpha(string $value): ?array { $value = trim($value); - if (preg_match('/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/', $value, $match) === 1) { - $hex = $match[1]; - if (strlen($hex) === 3) { - $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2]; + if (preg_match('/^#([0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/', $value, $match) === 1) { + return $this->parseHex(hex: $match[1]); + } + + if (preg_match('/^rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*([\d.]+)(%?)\s*)?\)$/', $value, $match) === 1) { + $alpha = 1.0; + if (isset($match[4]) === true && $match[4] !== '') { + $alpha = (float)$match[4]; + if (($match[5] ?? '') === '%') { + $alpha = ($alpha / 100); + } } return [ - hexdec(substr($hex, 0, 2)), - hexdec(substr($hex, 2, 2)), - hexdec(substr($hex, 4, 2)), + min(255, (int)$match[1]), + min(255, (int)$match[2]), + min(255, (int)$match[3]), + round(max(0.0, min(1.0, $alpha)), 3), ]; } - if (preg_match('/^rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*[\d.]+\s*)?\)$/', $value, $match) === 1) { - $red = min(255, (int)$match[1]); - $green = min(255, (int)$match[2]); - $blue = min(255, (int)$match[3]); + return null; + }//end parseColorWithAlpha() + + /** + * Expand and parse the digits of a 3, 4, 6 or 8 digit hex colour. + * + * @param string $hex The hex digits without the leading '#'. + * + * @return array{0: int, 1: int, 2: int, 3: float} The parsed colour. + */ + private function parseHex(string $hex): array { + if (strlen($hex) <= 4) { + $expanded = ''; + foreach (str_split($hex) as $digit) { + $expanded .= $digit . $digit; + } - return [$red, $green, $blue]; + $hex = $expanded; } - return null; - }//end parseColor() + $alpha = 1.0; + if (strlen($hex) === 8) { + $alpha = round(hexdec(substr($hex, 6, 2)) / 255, 3); + } + + return [ + (int)hexdec(substr($hex, 0, 2)), + (int)hexdec(substr($hex, 2, 2)), + (int)hexdec(substr($hex, 4, 2)), + $alpha, + ]; + }//end parseHex() + + /** + * Composite a colour with alpha over an opaque colour ("source over"). + * + * @param array{0: int, 1: int, 2: int, 3: float} $top The colour on top. + * @param array{0: int, 1: int, 2: int} $under The opaque colour underneath. + * + * @return array{0: int, 1: int, 2: int} The opaque colour that renders. + * + * @spec openspec/changes/authoring-token-value-types/tasks.md#task-3.1 + */ + public function blend(array $top, array $under): array { + $alpha = $top[3]; + $result = []; + foreach ([0, 1, 2] as $index) { + $result[] = (int)round(($top[$index] * $alpha) + ($under[$index] * (1 - $alpha))); + } + + return [$result[0], $result[1], $result[2]]; + }//end blend() + + /** + * Measure the contrast of a foreground over a background as it renders. + * + * A translucent background is blended over the page background first (the + * page itself over white), then a translucent foreground over that result. + * Opaque colours measure exactly as {@see self::ratio()} on their channels. + * + * @param string $foreground The foreground colour literal. + * @param string $background The background colour literal. + * @param string|null $page The page background under a translucent background; white when null or unparseable. + * + * @return float|null The unrounded ratio, or null when either colour is not a parseable literal. + * + * @spec openspec/changes/authoring-token-value-types/tasks.md#task-3.1 + */ + public function measure(string $foreground, string $background, ?string $page = null): ?float { + $fgRgba = $this->parseColorWithAlpha(value: $foreground); + $bgRgba = $this->parseColorWithAlpha(value: $background); + if ($fgRgba === null || $bgRgba === null) { + return null; + } + + $white = [255, 255, 255]; + $pageRgb = $white; + $pageRgba = null; + if ($page !== null) { + $pageRgba = $this->parseColorWithAlpha(value: $page); + } + + if ($pageRgba !== null) { + $pageRgb = $this->blend(top: $pageRgba, under: $white); + } + + $bgRgb = $this->blend(top: $bgRgba, under: $pageRgb); + $fgRgb = $this->blend(top: $fgRgba, under: $bgRgb); + + return $this->ratio(first: $fgRgb, second: $bgRgb); + }//end measure() }//end class diff --git a/lib/Service/CssInjectionService.php b/lib/Service/CssInjectionService.php index 5490cf7a..0778e11c 100644 --- a/lib/Service/CssInjectionService.php +++ b/lib/Service/CssInjectionService.php @@ -232,8 +232,8 @@ private function runLayer(string $layer, callable $work): void { * appconfig (see {@see isContextThemed()}). Otherwise this is the * verbatim former `Application::injectThemeCSS()` body: design-system * stylesheets in declared order, token set CSS, icon/error contrast - * fixes, custom overrides, custom fonts, then conditional - * hide-slogan/show-menu-labels stylesheets. + * fixes, the component scopes, custom overrides, custom fonts, then + * conditional hide-slogan/show-menu-labels/primary-lock stylesheets. * * @param string $context One of `user`/`login`/`guest`/`public`/`error`, * or any other value (always themed — fail open). @@ -281,7 +281,10 @@ public function inject(string $context): void { ); // 4/4.1. Custom overrides, then freeform custom CSS. - $this->runLayer(layer: 'override-styles', work: fn () => $this->injectOverrideStyles()); + $this->runLayer( + layer: 'override-styles', + work: fn () => $this->injectOverrideStyles(tokenSet: $tokenSet, designSystemId: $designSystemId) + ); // 4.5. Custom fonts. $this->runLayer( @@ -375,7 +378,7 @@ private function designSystemLayers(string $designSystemId, string $tokenSet): a // 3. Load token values (only when a design system reads --nldesign-* vars). if ($designSystemId === 'none') { - return $layers; + return array_merge($layers, $this->noDesignSystemLayers(tokenSet: $tokenSet)); } // 3a-1. The `nextcloud` set is the one set whose values belong to @@ -440,9 +443,60 @@ private function designSystemLayers(string $designSystemId, string $tokenSet): a // (see css/error-contrast.css). $layers[] = ['layer' => 'contrast', 'kind' => 'file', 'file' => 'error-contrast']; + // 3.9. Component scopes, last of the set layers: it captures the `:root` + // declarations the layers above make, and the admin's own overrides come + // after it and are allowed to move the brand values those captures resolve + // to. It rides with the set layers rather than beside them so `none` stays + // stock — that branch returns above — and so the manifest carries it, which + // is what lets the client add and remove it without a reload. + $layers[] = ['layer' => 'component-scopes', 'kind' => 'file', 'file' => 'component-scopes']; + return $layers; }//end designSystemLayers() + /** + * The set layers of a token set on the `none` design system. + * + * STOCK STILL GETS THE COMPONENT LAYER, and that is the difference between + * a themable instance and an inert one. + * + * `none` means "no design system", not "the app does nothing". Every + * instance STARTS on the stock `nextcloud` set, so without this the token + * editor and the playground could not move a single colour until an admin + * had already picked some other theme — you had to have a theme before you + * could make one. + * + * Emitting it costs nothing visually. This layer only REDIRECTS Nextcloud's + * own variables inside a component's subtree, and every component token is + * undeclared until somebody sets one, so an untouched stock instance + * resolves each redirect straight back to the captured global and renders + * byte-identically to stock. What it buys is that the overrides file — + * which is emitted after this list whatever the design system — finally has + * something reading the tokens it writes. + * + * A CUSTOM set on `none` is a theme saved off stock Nextcloud, and its file + * is the only place its values live — the header colour an admin set and + * saved as a theme, for one. The stock set itself has no file to load: it + * IS the running Nextcloud. Leaving the custom file out too put the saved + * theme in the dropdown and none of it on the page. + * + * @param string $tokenSet The token set id. + * + * @return array The entries, in cascade order. + * + * @spec openspec/specs/css-architecture/spec.md + */ + private function noDesignSystemLayers(string $tokenSet): array { + $layers = []; + if ($tokenSet !== self::STOCK_TOKEN_SET) { + $layers[] = ['layer' => 'tokens', 'kind' => 'file', 'file' => 'tokens/' . $tokenSet]; + } + + $layers[] = ['layer' => 'component-scopes', 'kind' => 'file', 'file' => 'component-scopes']; + + return $layers; + }//end noDesignSystemLayers() + /** * Emit one entry from {@see self::designSystemLayers()}. * @@ -533,13 +587,24 @@ public function getStylesheetManifest(string $tokenSet): array { * Emit the admin-authored override layers: the always-present * custom-overrides stylesheet, then the freeform custom CSS. * + * @param string $tokenSet The token set this page renders. + * @param string $designSystemId The design system that set wears. + * * @return void * + * @SuppressWarnings(PHPMD.StaticAccess) - CustomOverridesService::fileFor() is a pure lookup + * * @spec openspec/specs/css-architecture/spec.md */ - private function injectOverrideStyles(): void { + private function injectOverrideStyles(string $tokenSet, string $designSystemId): void { // 4. Custom overrides — admin-defined token overrides, always loaded last. // + // A set on no design system has an overrides file of its own and never + // loads the shared one, so nothing pinned on another theme can reach a + // page that is meant to be Nextcloud plus that set — see + // CustomOverridesService. + $overridesFile = CustomOverridesService::fileFor(tokenSet: $tokenSet, designSystemId: $designSystemId); + // // `ensureExists()` WRITES `css/custom-overrides.css` INSIDE THE APP // DIRECTORY, which is exactly the write a read-only or // root-owned-checkout deployment refuses (nldesign#264). It throws only @@ -549,11 +614,11 @@ private function injectOverrideStyles(): void { // runs, and the skip is logged rather than silent. $overridesReady = true; try { - $this->overridesService->ensureExists(); + $this->overridesService->ensureExists(tokenSet: $tokenSet); } catch (Throwable $e) { $overridesReady = false; $this->logger->warning( - 'nldesign: css/custom-overrides.css is absent and could not be created, so the custom-overrides ' + 'nldesign: css/' . $overridesFile . '.css is absent and could not be created, so the custom-overrides ' . 'layer was skipped. The app directory is not writable by the web server; generated CSS belongs ' . 'in appdata (see nldesign#264).', [ @@ -564,7 +629,7 @@ private function injectOverrideStyles(): void { } if ($overridesReady === true) { - $this->emitStyle(file: 'custom-overrides'); + $this->emitStyle(file: $overridesFile); } // 4.1 Freeform custom CSS — admin-authored arbitrary rules. Emitted @@ -604,11 +669,20 @@ private function injectCustomFontLink(string $designSystemId): void { }//end injectCustomFontLink() /** - * Emit the appconfig-gated hide-slogan and show-menu-labels stylesheets. + * Emit the appconfig-gated hide-slogan, show-menu-labels and primary-lock + * stylesheets. + * + * `primary-lock` is emitted LAST on purpose. It and `custom-overrides.css` + * both write `--nldesign-component-*` at `:root` with `!important`, so the + * later of the two wins — and while the setting is on, the brand primary is + * meant to beat a per-component value the admin stored earlier. Nothing is + * deleted: turning the setting off drops this layer and the stored values + * take effect again. * * @return void * * @spec openspec/specs/css-architecture/spec.md + * @spec openspec/specs/component-tokens/spec.md */ private function injectConditionalStyles(): void { if ($this->config->getAppValue(Application::APP_ID, 'hide_slogan', '0') === '1') { @@ -618,6 +692,10 @@ private function injectConditionalStyles(): void { if ($this->config->getAppValue(Application::APP_ID, 'show_menu_labels', '0') === '1') { $this->emitStyle(file: 'show-menu-labels'); } + + if ($this->config->getAppValue(Application::APP_ID, 'primary_drives_components', '0') === '1') { + $this->emitStyle(file: 'primary-lock'); + } }//end injectConditionalStyles() /** diff --git a/lib/Service/CssParserService.php b/lib/Service/CssParserService.php index 9fb5db8c..3b24bd17 100644 --- a/lib/Service/CssParserService.php +++ b/lib/Service/CssParserService.php @@ -210,6 +210,34 @@ public function parseRootBlock(string $css): array { return []; }//end parseRootBlock() + /** + * Read the light values out of a custom-overrides file. + * + * The file custom-overrides.css carries its light values in a `:root` block + * and, for brand colour overrides, the same tokens again in two dark blocks. Reading every + * declaration in the file would let the dark values overwrite the light + * ones on import. So a file with a `:root` block is read from that block + * alone; a file of bare declarations (a hand-written import) is read whole. + * + * @param string $css The raw file content. + * + * @return array|null Token => light value, or null when the file declares nothing. + * + * @spec openspec/changes/authoring-token-value-types/tasks.md#task-2.2 + */ + public function parseOverridesFile(string $css): ?array { + if (preg_match('/:root\s*\{/', $css) === 1) { + $root = $this->parseRootBlock(css: $css); + if (empty($root) === true) { + return null; + } + + return $root; + } + + return $this->parseDeclarations(content: $css); + }//end parseOverridesFile() + /** * Parse hand-authored dark-mode declarations from a top-level * `@media (prefers-color-scheme: dark) { :root { ... } }` block. diff --git a/lib/Service/CustomOverridesService.php b/lib/Service/CustomOverridesService.php index 94364ddf..2fe7a914 100644 --- a/lib/Service/CustomOverridesService.php +++ b/lib/Service/CustomOverridesService.php @@ -27,7 +27,9 @@ namespace OCA\Thematiq\Service; +use OCA\Thematiq\AppInfo\Application; use OCP\App\IAppManager; +use OCP\IConfig; use RuntimeException; /** @@ -37,11 +39,30 @@ * It validates all token names against the TokenRegistry before writing. * * The CSS file format is strictly controlled: - * - Single :root {} block + * - One :root {} block with the light values, read back by read() + * - For every brand-layer colour override, the two dark scopes of the generated dark + * stylesheets with its derived dark value, so a user who chose the dark + * theme (whose colours Nextcloud declares on body) and a user whose system + * is dark see the same colour * - One declaration per line * - Each declaration carries !important so user overrides win the cascade over * the nldesign design-system stylesheets and Nextcloud core theming - * - No selectors other than :root + * - No selectors other than :root and the two dark scopes + * + * A SET ON NO DESIGN SYSTEM NEVER READS THE SHARED FILE. + * + * `css/custom-overrides.css` holds the edits made on every set that wears a + * design system. A set on `none` — the stock `nextcloud` set, and every + * theme saved off it — keeps its edits in a file of its own, + * `css/custom-overrides-{set}.css`, and only that file is loaded while it is + * the set on the page. Such a set is Nextcloud plus its own values and + * nothing else: the stock set loads no token file at all, so emptying its + * edit file — the reset — gives stock back. + * + * Before the split there was one file, loaded whatever the set. A value an + * admin pinned while on some other theme stayed on the page after switching + * back to "Nextcloud", so the stock set was not stock, and every theme saved + * off it wore the other themes' pinned values too. * * @spec openspec/changes/retrofit-2026-05-24-annotate-nldesign/tasks.md#task-8 * @spec openspec/changes/retrofit-2026-05-24-annotate-nldesign/tasks.md#task-28 @@ -54,6 +75,22 @@ */ class CustomOverridesService { + /** + * The scope of a user whose system is dark and who chose no theme (as in + * the generated dark stylesheets). + * + * @var string + */ + private const SYSTEM_DARK_SELECTOR = 'body:not([data-theme-light]):not([data-theme-dark])' + . ':not([data-theme-light-highcontrast]):not([data-theme-dark-highcontrast])'; + + /** + * The scope of a user who chose the dark theme (as in the generated dark stylesheets). + * + * @var string + */ + private const CHOSEN_DARK_SELECTOR = 'body[data-theme-dark],' . PHP_EOL . 'body[data-themes*=dark]'; + /** * The CSS file header comment. * @@ -61,6 +98,13 @@ class CustomOverridesService { */ private const CSS_HEADER = '/* NL Design — custom token overrides. Generated by theme editor. Do not edit manually. */'; + /** + * The overrides file of every set that wears a design system (under `css/`, no extension). + * + * @var string + */ + public const FILE = 'custom-overrides'; + /** * The app manager for resolving the CSS file path. * @@ -75,40 +119,118 @@ class CustomOverridesService { */ private CssParserService $cssParser; + /** + * The dark palette, which derives a colour override's dark value exactly + * as the generated dark stylesheets do. + * + * @var DarkPaletteService + */ + private DarkPaletteService $darkPalette; + + /** + * The app config, for the active set when a caller names none. + * + * @var IConfig|null + */ + private ?IConfig $config; + + /** + * Which design system a set wears, for choosing its file. + * + * @var DesignSystemService|null + */ + private ?DesignSystemService $designSystems; + /** * Constructor. * * @param IAppManager $appManager The app manager. * @param CssParserService $cssParser CSS parser for :root block extraction. + * @param DarkPaletteService $darkPalette Derives each colour override's dark value. + * @param IConfig|null $config The app config. With the next one, what picks a set's own file; + * without them every set reads the shared file. + * @param DesignSystemService|null $designSystems The token set metadata. */ - public function __construct(IAppManager $appManager, CssParserService $cssParser) { + public function __construct( + IAppManager $appManager, + CssParserService $cssParser, + DarkPaletteService $darkPalette, + ?IConfig $config = null, + ?DesignSystemService $designSystems = null, + ) { $this->appManager = $appManager; $this->cssParser = $cssParser; + $this->darkPalette = $darkPalette; + $this->config = $config; + $this->designSystems = $designSystems; }//end __construct() /** - * Get the absolute path to custom-overrides.css. + * The overrides file a token set reads, under `css/` and without extension. + * + * @param string $tokenSet The token set id. + * @param string $designSystemId The design system the set wears. + * + * @return string `custom-overrides-{set}` for a set on `none`, {@see self::FILE} for every other. + * + * @spec openspec/specs/css-architecture/spec.md + */ + public static function fileFor(string $tokenSet, string $designSystemId): string { + // The stock set is on `none` by definition; named here too so its + // file does not depend on its metadata being readable. + if ($designSystemId !== 'none' && $tokenSet !== CssInjectionService::STOCK_TOKEN_SET) { + return self::FILE; + } + + return self::FILE . '-' . preg_replace('/[^a-z0-9-]/', '', strtolower($tokenSet)); + }//end fileFor() + + /** + * Get the absolute path to the overrides file of a token set. + * + * @param string|null $tokenSet The token set id, or null for the instance's active set. * * @return string The CSS file path. + * + * @SuppressWarnings(PHPMD.StaticAccess) - fileFor() is static so CssInjectionService can ask without an instance */ - private function getFilePath(): string { - return $this->appManager->getAppPath('thematiq') . '/css/custom-overrides.css'; + private function getFilePath(?string $tokenSet): string { + // Built without the set lookup (a caller that constructs the service + // by hand): the shared file, which is every set's on a design system. + if ($this->config === null || $this->designSystems === null) { + return $this->appManager->getAppPath('thematiq') . '/css/' . self::FILE . '.css'; + } + + if ($tokenSet === null) { + $tokenSet = $this->config->getAppValue( + Application::APP_ID, + 'token_set', + CssInjectionService::STOCK_TOKEN_SET + ); + } + + $meta = $this->designSystems->getTokenSetMeta(tokenSetId: $tokenSet); + $file = self::fileFor(tokenSet: $tokenSet, designSystemId: (string)($meta['design_system'] ?? 'nldesign')); + + return $this->appManager->getAppPath('thematiq') . '/css/' . $file . '.css'; }//end getFilePath() /** - * Ensure the custom-overrides.css file exists. + * Ensure the overrides file of a token set exists. * * Creates an empty :root {} file if the file is absent. * Safe to call on every page load (no-op if file already exists). * + * @param string|null $tokenSet The token set id, or null for the instance's active set. + * * @return void * * @spec openspec/changes/retrofit-2026-05-24-annotate-nldesign/tasks.md#task-28 */ - public function ensureExists(): void { - $path = $this->getFilePath(); + public function ensureExists(?string $tokenSet = null): void { + $path = $this->getFilePath(tokenSet: $tokenSet); if (file_exists($path) === false) { - $this->writeFile(tokens: []); + $this->writeFile(tokens: [], path: $path); } }//end ensureExists() @@ -119,12 +241,14 @@ public function ensureExists(): void { * Returns only the tokens explicitly set in the file. * Does not return defaults or resolved values from the full CSS stack. * + * @param string|null $tokenSet The token set id, or null for the instance's active set. + * * @return array Map of token name => value for all overrides in the file. * * @spec openspec/changes/retrofit-2026-05-24-annotate-nldesign/tasks.md#task-29 */ - public function read(): array { - $path = $this->getFilePath(); + public function read(?string $tokenSet = null): array { + $path = $this->getFilePath(tokenSet: $tokenSet); if (file_exists($path) === false) { return []; } @@ -138,12 +262,13 @@ public function read(): array { }//end read() /** - * Write a new set of token overrides to custom-overrides.css. + * Write a new set of token overrides to the overrides file of a token set. * * Only tokens present in TokenRegistry are accepted — others are silently ignored. * Writes atomically via a temp file + rename to avoid partial writes. * - * @param array $tokens Map of token name => value to persist. + * @param array $tokens Map of token name => value to persist. + * @param string|null $tokenSet The token set id, or null for the instance's active set. * * @return void * @@ -151,13 +276,57 @@ public function read(): array { * * @spec openspec/changes/retrofit-2026-05-24-annotate-nldesign/tasks.md#task-30 */ - public function write(array $tokens): void { + public function write(array $tokens, ?string $tokenSet = null): void { $validated = $this->filterEditable(tokens: $tokens); - $this->writeFile(tokens: $validated); + $this->writeFile(tokens: $validated, path: $this->getFilePath(tokenSet: $tokenSet)); }//end write() + /** + * List the tokens in a map that write() would not persist, with the reason. + * + * A token is refused when its name is not in the TokenRegistry, when its value + * is not a string, or when its value carries a character the writer strips to + * keep the file a single :root block. Callers that answer an admin use this to + * refuse the whole save instead of reporting tokens that never reached the file. + * + * @param array $tokens Input token map. + * + * @return array Map of refused token name => reason; empty when all are accepted. + * + * @SuppressWarnings(PHPMD.StaticAccess) - TokenRegistry uses static methods by design + * + * @spec openspec/changes/authoring-token-value-types/tasks.md#task-2.1 + */ + public function findRejected(array $tokens): array { + $rejected = []; + foreach ($tokens as $name => $value) { + $name = (string)$name; + if (TokenRegistry::isEditable(tokenName: $name) === false) { + $rejected[$name] = 'not an editable token'; + continue; + } + + if (is_string($value) === false || $this->isUnsafeValue(value: $value) === true) { + $rejected[$name] = 'not an allowed value'; + } + } + + return $rejected; + }//end findRejected() + + /** + * Tell whether a value carries a character that would break out of the :root block. + * + * @param string $value The token value. + * + * @return bool True when the writer would drop the value. + */ + private function isUnsafeValue(string $value): bool { + return preg_match('/[{};]|\/\*|\*\//', $value) === 1; + }//end isUnsafeValue() + /** * Filter a token map to only those present in the registry. * @@ -184,6 +353,7 @@ private function filterEditable(array $tokens): array { * Write the CSS file atomically using a temp file + rename. * * @param array $tokens Validated token map to write. + * @param string $path The file to write. * * @return void * @@ -191,8 +361,7 @@ private function filterEditable(array $tokens): array { * * @spec openspec/changes/retrofit-2026-05-24-annotate-nldesign/tasks.md#task-31 */ - private function writeFile(array $tokens): void { - $path = $this->getFilePath(); + private function writeFile(array $tokens, string $path): void { $tmpPath = $path . '.tmp'; $css = $this->buildCss(tokens: $tokens); @@ -233,10 +402,62 @@ private function buildCss(array $tokens): string { } $lines = $this->buildDeclarationLines(tokens: $tokens); + $css = $header . ':root {' . PHP_EOL . implode(PHP_EOL, $lines) . PHP_EOL . '}' . PHP_EOL; - return $header . ':root {' . PHP_EOL . implode(PHP_EOL, $lines) . PHP_EOL . '}' . PHP_EOL; + $darkLines = $this->buildDeclarationLines(tokens: $this->darkValues(tokens: $tokens)); + if (empty($darkLines) === true) { + return $css; + } + + // The same two scopes the generated dark stylesheets use. A user who + // chose the dark theme gets Nextcloud's dark colours declared on body, + // which a :root value never reaches; a body-level declaration wins for + // both kinds of dark user. + $css .= '@media (prefers-color-scheme: dark) {' . PHP_EOL + . ' ' . self::SYSTEM_DARK_SELECTOR . ' {' . PHP_EOL + . ' ' . implode(PHP_EOL . ' ', $darkLines) . PHP_EOL + . ' }' . PHP_EOL + . '}' . PHP_EOL + . self::CHOSEN_DARK_SELECTOR . ' {' . PHP_EOL + . implode(PHP_EOL, $darkLines) . PHP_EOL + . '}' . PHP_EOL; + + return $css; }//end buildCss() + /** + * The dark value of every brand-layer colour override: derived as the + * generated dark stylesheets derive it, or the light value when it is not + * a colour literal, so both kinds of dark user still see the same thing. + * + * Only the brand layer (Nextcloud's own variables) is split today, because + * only those does Nextcloud re-declare on body for a chosen theme. Component + * tokens are left out: both kinds of dark user already get the same body + * value from the generated dark stylesheet, and a body-level copy here would + * outrank the primary-lock layer, which locks them at :root. + * + * @param array $tokens Token name => light value. + * + * @return array Colour token name => dark value. + * + * @SuppressWarnings(PHPMD.StaticAccess) - TokenRegistry uses static methods by design + * + * @spec openspec/changes/authoring-token-value-types/tasks.md#task-2.2 + */ + private function darkValues(array $tokens): array { + $registry = TokenRegistry::getTokens(); + $dark = []; + foreach ($tokens as $name => $value) { + if (($registry[$name]['type'] ?? '') !== 'color' || ($registry[$name]['group'] ?? '') !== 'brand') { + continue; + } + + $dark[$name] = ($this->darkPalette->deriveDarkValue(token: $name, lightValue: $value, context: $tokens) ?? $value); + } + + return $dark; + }//end darkValues() + /** * Build individual CSS declaration lines from a token map. * @@ -250,7 +471,7 @@ private function buildDeclarationLines(array $tokens): array { $lines = []; foreach ($tokens as $name => $value) { // Reject any value containing CSS injection characters. - if (preg_match('/[{};]|\/\*|\*\//', $value) === 1) { + if ($this->isUnsafeValue(value: $value) === true) { continue; } @@ -293,12 +514,14 @@ private function parseDeclarations(string $css): array { /** * Return the raw CSS file content for download. * + * @param string|null $tokenSet The token set id, or null for the instance's active set. + * * @return string The raw file content, or an empty :root {} if the file does not exist. * * @spec openspec/changes/retrofit-2026-05-24-annotate-nldesign/tasks.md#task-34 */ - public function getRawContent(): string { - $path = $this->getFilePath(); + public function getRawContent(?string $tokenSet = null): string { + $path = $this->getFilePath(tokenSet: $tokenSet); if (file_exists($path) === false) { return self::CSS_HEADER . PHP_EOL . ':root {}' . PHP_EOL; } diff --git a/lib/Service/CustomTokenSetService.php b/lib/Service/CustomTokenSetService.php index 1f674d44..e18d6b50 100644 --- a/lib/Service/CustomTokenSetService.php +++ b/lib/Service/CustomTokenSetService.php @@ -173,6 +173,9 @@ public function slugify(string $name): string { * to be written under `img/logos/`. Nextcloud's core theming takes a * logo as a FILE (`ImageManager::updateImage()`), so a theme's inline * `data:` URI has to become one before `theming.logo` can mean anything. + * @param string|null $designSystem The design system this set was created FROM, when the caller knows it. + * Decides which stylesheet layers the set emits; left unset for an + * upload of an unknown document, which still resolves to nldesign. * * @return array{id: string, warnings: array>} The result. * @@ -197,6 +200,7 @@ public function store( ?string $css = null, array $theming = [], ?array $logoAsset = null, + ?string $designSystem = null, ): array { $slug = $this->slugify(name: $displayName); if ($slug === '') { @@ -249,6 +253,22 @@ public function store( $entry['importWarnings'] = $importWarnings; } + // The design system this set was MADE from, when the caller knows it. + // + // Without it every custom set resolved to `nldesign`, so a theme saved + // from the stock Nextcloud set came back with the whole NL Design System + // layer on top of it — fonts, defaults.css, element-overrides.css — and + // an admin who had changed one colour found their instance rebuilt. + // Recording it keeps "save this as a theme" faithful to what was saved. + // + // Left unset when the caller has no opinion (an upload of an unknown + // document is exactly that), and an unset value still resolves to + // `nldesign` downstream, which is what every set written before this + // field existed already was. + if ($designSystem !== null && $designSystem !== '') { + $entry['design_system'] = $designSystem; + } + $manifest = $this->getManifest(); $manifest[$id] = $entry; $this->saveManifest(manifest: $manifest); diff --git a/lib/Service/DarkPaletteService.php b/lib/Service/DarkPaletteService.php index 61efb9f7..3fbf507c 100644 --- a/lib/Service/DarkPaletteService.php +++ b/lib/Service/DarkPaletteService.php @@ -65,8 +65,11 @@ class DarkPaletteService { * {@see self::resolveAlias()}) and classifies text-class tokens by the * `-color` / `-background-color` convention the utrecht and municipal * families use, not only by the word "text". + * + * Version 3 keeps a translucent light value's alpha on its dark value + * (`#rrggbbaa`) and darkens 8-digit hex tokens, which it used to skip. */ - public const GENERATOR_VERSION = 2; + public const GENERATOR_VERSION = 3; /** * The guaranteed-passing near-white snap value (matches NC's own dark @@ -311,19 +314,44 @@ public function deriveDarkDeclarations(array $lightDeclarations): array { // propagates through this alias in dark mode; the alternative is a // dark mode that only ever half-applies. $literal = $this->resolveAlias(value: $value, declarations: $lightDeclarations); - $rgb = $this->contrast->parseColor(value: $literal); - if ($rgb === null) { + $darkValue = $this->deriveDarkValue(token: $token, lightValue: $literal, context: $lightDeclarations); + if ($darkValue === null) { // Unparseable (gradient, keyword, size, font stack, url(), an // alias chain with no literal at the end) — skip. continue; } - $dark[$token] = $this->deriveColorToken(token: $token, rgb: $rgb, lightDeclarations: $lightDeclarations); + $dark[$token] = $darkValue; } return $this->regenerateRgbCompanions(lightDeclarations: $lightDeclarations, darkDeclarations: $dark); }//end deriveDarkDeclarations() + /** + * Derive the dark value of one colour literal, as the generated dark + * stylesheets do, so the token editor and the generator never disagree. + * + * The dark channels come from the opaque colour; a translucent light value + * keeps its alpha, so an overlay stays an overlay in dark mode. + * + * @param string $token The token name (decides text-class or surface-class). + * @param string $lightValue The light colour literal. + * @param array $context The light declarations around it, for the brand-primary exception. + * + * @return string|null The dark hex value, or null when the value is not a colour literal. + * + * @spec openspec/changes/authoring-token-value-types/tasks.md#task-2.3 + */ + public function deriveDarkValue(string $token, string $lightValue, array $context = []): ?string { + $rgba = $this->contrast->parseColorWithAlpha(value: $lightValue); + if ($rgba === null) { + return null; + } + + return $this->deriveColorToken(token: $token, rgb: [$rgba[0], $rgba[1], $rgba[2]], lightDeclarations: $context) + . $this->alphaSuffix(alpha: $rgba[3]); + }//end deriveDarkValue() + /** * Follow a `var()` alias chain to the literal it ends at. * @@ -441,13 +469,12 @@ private function brandPrimaryPasses(array $lightDeclarations): bool { return true; } - $bgRgb = $this->contrast->parseColor(value: $background); - $fgRgb = $this->contrast->parseColor(value: $foreground); - if ($bgRgb === null || $fgRgb === null) { + $ratio = $this->contrast->measure(foreground: $foreground, background: $background); + if ($ratio === null) { return true; } - return ($this->contrast->ratio(first: $fgRgb, second: $bgRgb) >= 4.5); + return ($ratio >= 4.5); }//end brandPrimaryPasses() /** @@ -1217,6 +1244,23 @@ private function rgbToHex(array $rgb): string { ); }//end rgbToHex() + /** + * The fourth hex pair for an alpha below 1, or '' for an opaque colour. + * + * @param float $alpha The alpha, 0-1. + * + * @return string Two lowercase hex digits, or an empty string. + * + * @spec openspec/changes/authoring-token-value-types/tasks.md#task-3.2 + */ + private function alphaSuffix(float $alpha): string { + if ($alpha >= 1.0) { + return ''; + } + + return sprintf('%02x', $this->clampInt(value: (int)round($alpha * 255))); + }//end alphaSuffix() + /** * Clamp a float into [min, max]. * diff --git a/lib/Service/DesignSystemService.php b/lib/Service/DesignSystemService.php index 522c2cb6..2a410dbc 100644 --- a/lib/Service/DesignSystemService.php +++ b/lib/Service/DesignSystemService.php @@ -77,6 +77,13 @@ class DesignSystemService { */ private ?array $tokenSetMeta = null; + /** + * The admin-created sets' manifest, decoded once per request. + * + * @var array|null + */ + private ?array $customTokenSetMeta = null; + /** * Constructor. * @@ -157,9 +164,67 @@ public function getTokenSetMeta(string $tokenSetId): array { $this->tokenSetMeta = $this->readJsonManifest(path: $path); } - return $this->tokenSetMeta[$tokenSetId] ?? []; + if (isset($this->tokenSetMeta[$tokenSetId]) === true) { + return $this->tokenSetMeta[$tokenSetId]; + } + + return $this->getCustomTokenSetMeta(tokenSetId: $tokenSetId); }//end getTokenSetMeta() + /** + * A custom set's manifest entry, from appconfig rather than a shipped file. + * + * Custom sets used to answer `[]` here, so every one of them fell back to + * `nldesign` — and that quietly decided far more than the token list did. A + * theme saved from the stock Nextcloud set came back wearing Fira Sans, + * `defaults.css` and `element-overrides.css`, because "no metadata" was read + * as "NL Design System". An admin who saved the stock theme with one colour + * changed did not get the stock theme with one colour changed. + * + * So a custom set now carries the design system it was CREATED from, and + * this reads it back. One written before that field existed still has no + * `design_system`, and still resolves to `nldesign` through the same `??` in + * CssInjectionService — which is what those sets have always been, so they + * do not change under an admin who is not expecting it. + * + * Read straight from the appconfig key rather than through + * CustomTokenSetService: that service reaches back into this one, and this + * lookup runs on every themed page render. + * + * @param string $tokenSetId The token set identifier. + * + * @return array The manifest entry, or [] when there is none. + * + * @spec openspec/specs/custom-token-sets/spec.md + */ + private function getCustomTokenSetMeta(string $tokenSetId): array { + if (str_starts_with($tokenSetId, CustomTokenSetService::ID_PREFIX) === false) { + return []; + } + + if ($this->customTokenSetMeta === null) { + $raw = $this->config->getAppValue( + Application::APP_ID, + CustomTokenSetService::MANIFEST_KEY, + '{}' + ); + + $decoded = json_decode($raw, true); + if (is_array($decoded) === false) { + $decoded = []; + } + + $this->customTokenSetMeta = $decoded; + } + + $entry = ($this->customTokenSetMeta[$tokenSetId] ?? []); + if (is_array($entry) === false) { + return []; + } + + return $entry; + }//end getCustomTokenSetMeta() + /** * Get a design system's icon pack, normalized to an ordered list. * diff --git a/lib/Service/DesignTokensMapper.php b/lib/Service/DesignTokensMapper.php index 0c651531..a9421bff 100644 --- a/lib/Service/DesignTokensMapper.php +++ b/lib/Service/DesignTokensMapper.php @@ -613,12 +613,19 @@ private function serializeColorObject(array $value): array { return ['ok' => false, 'reason' => 'unsupported-color-space', 'detail' => ($value['colorSpace'] ?? 'unknown')]; } + $alphaHex = $this->alphaToHex(alpha: $value['alpha'] ?? null); + if (isset($value['hex']) === true && is_string($value['hex']) === true) { $hex = $value['hex']; if (str_starts_with($hex, '#') === false) { $hex = '#' . $hex; } + // The hex member is a 6-digit fallback with no alpha of its own. + if (strlen($hex) === 7) { + $hex .= $alphaHex; + } + return ['ok' => true, 'value' => $hex]; } @@ -626,31 +633,79 @@ private function serializeColorObject(array $value): array { && is_array($value['components']) === true && count($value['components']) >= 3 ) { - return ['ok' => true, 'value' => $this->componentsToHex(components: array_values($value['components']))]; + $hex = $this->componentsToHex( + components: array_values($value['components']), + linear: $colorSpace === 'srgb-linear' + ); + + return ['ok' => true, 'value' => $hex . $alphaHex]; } return ['ok' => false, 'reason' => 'unsupported-value-shape']; }//end serializeColorObject() /** - * Serialize three 0–1 sRGB float components to a `#rrggbb` hex literal. + * Serialize three 0–1 sRGB-family float components to a `#rrggbb` hex literal. + * + * Linear-light (`srgb-linear`) components go through the sRGB transfer + * function first; scaling them straight to 0–255 renders them too dark. * * @param array $components The `[r, g, b]` components (0–1 range, clamped). + * @param bool $linear Whether the components are linear light. * * @return string The `#rrggbb` hex literal. * - * @spec openspec/specs/custom-token-sets/spec.md + * @spec openspec/changes/authoring-dtcg-export/tasks.md#task-2.2 */ - private function componentsToHex(array $components): string { + private function componentsToHex(array $components, bool $linear): string { $hex = '#'; foreach ([0, 1, 2] as $index) { $channel = max(0.0, min(1.0, (float)$components[$index])); + if ($linear === true) { + $channel = $this->linearToSrgb(channel: $channel); + } + $hex .= str_pad(dechex((int)round($channel * 255)), 2, '0', STR_PAD_LEFT); } return $hex; }//end componentsToHex() + /** + * Apply the sRGB transfer function (CSS Color 4) to one linear-light channel. + * + * @param float $channel The linear channel, 0–1. + * + * @return float The gamma-encoded sRGB channel, 0–1. + */ + private function linearToSrgb(float $channel): float { + if ($channel <= 0.0031308) { + return 12.92 * $channel; + } + + return (1.055 * ($channel ** (1 / 2.4))) - 0.055; + }//end linearToSrgb() + + /** + * Turn a colour object's `alpha` into the fourth hex pair, or '' when opaque. + * + * @param mixed $alpha The `alpha` member (0–1), absent for an opaque colour. + * + * @return string Two hex digits, or an empty string for an opaque or absent alpha. + */ + private function alphaToHex(mixed $alpha): string { + if (is_int($alpha) === false && is_float($alpha) === false) { + return ''; + } + + $alpha = max(0.0, min(1.0, (float)$alpha)); + if ($alpha >= 1.0) { + return ''; + } + + return str_pad(dechex((int)round($alpha * 255)), 2, '0', STR_PAD_LEFT); + }//end alphaToHex() + /** * Serialize a `dimension` value: legacy string passthrough, or the * `{value, unit}` object form concatenated as ``. diff --git a/lib/Service/PlaygroundStateService.php b/lib/Service/PlaygroundStateService.php index fe96cb98..a3a231c7 100644 --- a/lib/Service/PlaygroundStateService.php +++ b/lib/Service/PlaygroundStateService.php @@ -61,23 +61,73 @@ class PlaygroundStateService { */ private TokenSetConverterService $converter; + /** + * The running instance's own stock theme, as tokens. + * + * Only consulted for the stock set, whose values live in the instance rather + * than in a file — see {@see getExportTokens()}. + * + * @var StockTokensService + */ + private StockTokensService $stockTokens; + /** * Constructor. * * @param IAppManager $appManager Resolves the app directory. * @param TokenSetPreviewService $previewValues Resolves token values and sources. * @param TokenSetConverterService $converter The conversion reason vocabulary. + * @param StockTokensService $stockTokens The instance's own stock theme. */ public function __construct( IAppManager $appManager, TokenSetPreviewService $previewValues, TokenSetConverterService $converter, + StockTokensService $stockTokens, ) { $this->appManager = $appManager; $this->previewValues = $previewValues; $this->converter = $converter; + $this->stockTokens = $stockTokens; }//end __construct() + /** + * The base an export writes: what the active set DECLARES, nothing else. + * + * Deliberately not `getResolvedTokens()`, which is what the instrument draws + * with. That map merges `css/systems/nldesign/defaults.css` UNDER the set — + * 200 tokens, 115 of them component tokens, all carrying Rijkshuisstijl + * values — so exporting from it wrote a theme full of decisions the admin + * never made and the set never held. `getDeclaredTokens()`'s own docblock + * says as much: a merged map "answers yes for every token, because the + * defaults declare them all". + * + * The stock set is the exception, and it is the whole reason this is a + * method rather than one more call in the list. Its values are not in a file + * — `css/tokens/nextcloud.css` is a snapshot of one Nextcloud version, kept + * as a fallback — they are in the running instance. Saving "the Nextcloud + * theme plus my change" has to write what THIS server is wearing, so the + * stock resolver answers for it and the file answers only if that fails. + * + * @param string $tokenSetId The token set the page is wearing. + * + * @return array Map of `--nldesign-*` token name => declared value. + * + * @spec openspec/specs/token-sets/spec.md + */ + private function getExportTokens(string $tokenSetId): array { + if ($tokenSetId !== CssInjectionService::STOCK_TOKEN_SET) { + return $this->previewValues->getDeclaredTokens(tokenSetId: $tokenSetId); + } + + $stock = $this->stockTokens->getTokens(); + if ($stock !== []) { + return $stock; + } + + return $this->previewValues->getDeclaredTokens(tokenSetId: $tokenSetId); + }//end getExportTokens() + /** * The initial-state keys the instrument reads. * @@ -92,6 +142,7 @@ public function getInitialState(string $tokenSetId): array { 'playgroundInventory' => $this->getInventory(), 'playgroundReasons' => $this->getReasons(), 'playgroundTokens' => $this->previewValues->getResolvedTokens(tokenSetId: $tokenSetId), + 'playgroundExportTokens' => $this->getExportTokens(tokenSetId: $tokenSetId), 'playgroundTokenSources' => $this->previewValues->getTokenSources(), 'playgroundVersion' => $this->getServerMajor(), // The set these values came from, so an export is named after the diff --git a/lib/Service/ShippedTokenSetAuditService.php b/lib/Service/ShippedTokenSetAuditService.php index 7587a24f..2cd46803 100644 --- a/lib/Service/ShippedTokenSetAuditService.php +++ b/lib/Service/ShippedTokenSetAuditService.php @@ -424,13 +424,17 @@ private function pairRatio(array $declarations, string $foreground, string $back return null; } - $fgRgb = $this->contrast->parseColor(value: $fgValue); - $bgRgb = $this->contrast->parseColor(value: $bgValue); - if ($fgRgb === null || $bgRgb === null) { + // Measured as it renders, so a translucent colour is blended first. + $ratio = $this->contrast->measure( + foreground: $fgValue, + background: $bgValue, + page: ($declarations['--nldesign-color-background'] ?? null) + ); + if ($ratio === null) { return null; } - return round($this->contrast->ratio(first: $fgRgb, second: $bgRgb), 2); + return round($ratio, 2); }//end pairRatio() /** diff --git a/lib/Service/StockTokensService.php b/lib/Service/StockTokensService.php index 1d06b1bb..52deae53 100644 --- a/lib/Service/StockTokensService.php +++ b/lib/Service/StockTokensService.php @@ -220,9 +220,38 @@ private function cacheKey(): string { * @return string|null The stylesheet body. */ private function build(): ?string { + $declarations = $this->getTokens(); + if ($declarations === []) { + return null; + } + + $body = ''; + foreach ($declarations as $name => $value) { + $body .= $name . ':' . $value . ';'; + } + + return ':root{' . $body . '}'; + }//end build() + + /** + * The running instance's stock theme as a `--nldesign-*` => value map. + * + * The same values {@see getCss()} serialises, as data. The export path needs + * them in this shape: saving the stock theme as a token set has to write what + * THIS Nextcloud is actually wearing, and the shipped `css/tokens/nextcloud.css` + * is only a snapshot of one version. + * + * Not memoised here — `getCss()` owns the caching, and this is called once per + * admin page render at most. + * + * @return array Map of `--nldesign-*` token name => stock value, sorted by name. + * + * @spec openspec/specs/token-sets/spec.md + */ + public function getTokens(): array { $stock = $this->flatten(vars: $this->stockVariables()); if ($stock === []) { - return null; + return []; } // The getTokenSources() map runs --color-X => --nldesign-Y, which is the @@ -250,19 +279,10 @@ private function build(): ?string { $declarations[$nldesignToken] = $stock[$this->canonical(token: $nldesignToken, candidates: $colorTokens)]; } - if ($declarations === []) { - return null; - } - ksort($declarations); - $body = ''; - foreach ($declarations as $name => $value) { - $body .= $name . ':' . $value . ';'; - } - - return ':root{' . $body . '}'; - }//end build() + return $declarations; + }//end getTokens() /** * Which of the variables reading this token holds its defining value. diff --git a/lib/Service/ThemingService.php b/lib/Service/ThemingService.php index fcd7325b..65587dd6 100644 --- a/lib/Service/ThemingService.php +++ b/lib/Service/ThemingService.php @@ -130,7 +130,7 @@ public function validateImagePaths(array $params): ?string { // theming — Nextcloud core has a single logo slot // (nextcloud/server#47357); the dark logo is delivered by nldesign's // own generated dark stylesheet instead. - foreach (['logo', 'background', 'logo_dark'] as $imageKey) { + foreach (['logo', 'logoheader', 'favicon', 'background', 'logo_dark'] as $imageKey) { if (isset($params[$imageKey]) === true && $params[$imageKey] !== '') { $error = $this->validateSinglePath( imageKey: $imageKey, @@ -142,6 +142,11 @@ public function validateImagePaths(array $params): ?string { } } + $mode = ($params['background_mode'] ?? ''); + if ($mode !== '' && in_array($mode, ['image', 'color', 'default'], true) === false) { + return "Invalid background_mode: $mode"; + } + return null; }//end validateImagePaths() @@ -196,13 +201,49 @@ public function applyColors(array $params): array { } } - // A plain background colour only shows when there is no background - // IMAGE: core paints its default image blob over the colour until the - // admin presses "Remove background image", which is exactly this - // app value. Set it here unless the same request also brings a - // background image, which applyImages() writes (and whose mime then - // overrides this) right after. - if (in_array('background_color', $updated, true) === true + return $this->applyBackgroundMode(params: $params, updated: $updated); + }//end applyColors() + + /** + * Put the background in the state the theme asks for, after its colours. + * + * @param array $params The request parameters. + * @param array $updated The colour keys applyColors() already wrote. + * + * @return array The updated keys, plus `background_mode` when a mode was applied. + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-nldesign/tasks.md#task-43 + */ + private function applyBackgroundMode(array $params, array $updated): array { + // A theme that CAPTURED Nextcloud's branding says which of the three + // background states it was saved in, and gets exactly that back: + // "Remove background image" (`color`), Nextcloud's own default image + // (`default`), or an image of its own (`image`, which applyImages() + // writes right after). + $mode = ($params['background_mode'] ?? ''); + if ($mode === 'color') { + $this->themingDefaults->set(setting: 'backgroundMime', value: 'backgroundColor'); + $updated[] = 'background_mode'; + + return $updated; + } + + if ($mode === 'default') { + $this->themingDefaults->undo(setting: 'background'); + $updated[] = 'background_mode'; + + return $updated; + } + + // Without a mode — every set that did not capture a panel — a plain + // background colour only shows when there is no background IMAGE: + // core paints its default image blob over the colour until the admin + // presses "Remove background image", which is exactly this app value. + // Set it here unless the same request also brings a background image, + // which applyImages() writes (and whose mime then overrides this) + // right after. + if ($mode === '' + && in_array('background_color', $updated, true) === true && (isset($params['background']) === false || $params['background'] === '') ) { $this->themingDefaults->set(setting: 'backgroundMime', value: 'backgroundColor'); @@ -233,7 +274,7 @@ public function applyColors(array $params): array { public function applyImages(array $params): array { $updated = []; - foreach (['logo', 'background'] as $imageKey) { + foreach (['logo', 'logoheader', 'favicon', 'background'] as $imageKey) { if (isset($params[$imageKey]) === true && $params[$imageKey] !== '') { $appPath = $this->appManager->getAppPath(appId: 'thematiq'); $fullPath = $appPath . '/' . $params[$imageKey]; @@ -248,11 +289,12 @@ public function applyImages(array $params): array { /** * The settings a reset to stock Nextcloud undoes, in the order core's own - * panel would: colours first, then the two image slots. + * panel would: colours first, then the image slots. The navigation-bar + * logo and the favicon are among them because a theme can now bring them. * * @var string[] */ - public const RESETTABLE = ['primary_color', 'background_color', 'logo', 'background']; + public const RESETTABLE = ['primary_color', 'background_color', 'logo', 'logoheader', 'favicon', 'background']; /** * Undo everything Thematiq may have synced into Nextcloud theming. diff --git a/lib/Service/TokenRegistry.php b/lib/Service/TokenRegistry.php index e5a5aebd..54901e78 100644 --- a/lib/Service/TokenRegistry.php +++ b/lib/Service/TokenRegistry.php @@ -23,7 +23,7 @@ namespace OCA\Thematiq\Service; /** - * Canonical registry of editable Nextcloud CSS custom properties. + * Canonical registry of editable CSS custom properties. * * This class is the single source of truth for: * - Which tokens the editor exposes for editing @@ -31,35 +31,154 @@ * - Which type of input to render (color picker or text field) * - The human-readable label for each token * + * TWO LAYERS, AND THE DIFFERENCE MATTERS + * -------------------------------------- + * BRAND tokens are Nextcloud's own globals — `--color-primary`, + * `--color-background-hover`, `--border-radius-element`. They are hand-listed in + * the four methods below, and moving one is MEANT to move every component that + * has not been given a value of its own. + * + * COMPONENT tokens are `--nldesign-component-*`, and they are not listed here at + * all: they are read from `scripts/mapping/component-tokens.json`, the same file + * `scripts/generate-component-scopes.mjs` turns into `css/component-scopes.css`. + * One table, two runtimes, so a token cannot be editable without a stylesheet + * that applies it — or applied by a stylesheet with no way to edit it. + * + * WHY THE COMPONENT LAYER EXISTS. Nextcloud has around sixty globals and every + * component draws from them, so `--color-primary-element` paints the primary + * button, the selected navigation entry, the sidebar's active tab, a focused + * text input, the checked checkbox, the progress bar, the dialog's confirm + * button and the counter bubble. Before this layer the editor could only write + * that global, so the playground's `Primary button` chip moved all eight. + * * Tokens marked "intentionally not overridden" in overrides.css MUST NOT appear here. * The excluded list covers dark-mode vars, auto-calculated values, and layout constants. * * Tabs: login | content | status | typography * Types: color | text + * Groups: `brand`, or the id of the component the token belongs to * * @spec openspec/changes/retrofit-2026-05-24-annotate-nldesign/tasks.md#task-45 * @spec openspec/changes/retrofit-2026-05-24-annotate-nldesign/tasks.md#task-46 * @spec openspec/changes/retrofit-2026-05-24-annotate-nldesign/tasks.md#task-47 */ class TokenRegistry implements TokenRegistryInterface { + /** + * The component-token table, relative to the app root. + * + * Read rather than injected because this class is static by design and the + * table is a shipped file, not configuration. `__DIR__` is `lib/Service`. + */ + private const COMPONENT_TOKENS_PATH = __DIR__ . '/../../scripts/mapping/component-tokens.json'; + + /** + * Decoded component-token table, or null before the first read. + * + * The registry is asked for on every admin page render and the table is + * parsed from disk, so the result is kept for the rest of the request. + * + * @var array|null + */ + private static ?array $componentTokens = null; + /** * Returns the full registry of editable tokens. * * Keys are CSS custom property names (e.g. '--color-primary'). - * Values are arrays with 'tab', 'type', and 'label' keys. + * Values carry 'tab', 'type', 'label', 'group' and 'primary'. * - * @return array The token registry. + * @return array The token registry. * * @spec openspec/changes/retrofit-2026-05-24-annotate-nldesign/tasks.md#task-45 */ public static function getTokens(): array { - return array_merge( + return array_merge(self::getBrandTokens(), self::getComponentTokens()); + }//end getTokens() + + /** + * Returns the brand layer: Nextcloud's own global custom properties. + * + * These are the values every component falls back to. They carry no + * component of their own, so they are grouped under `brand` and are never + * locked by the `primary_drives_components` setting — that setting exists to + * make these win, not to freeze them. + * + * @return array The brand tokens. + * + * @spec openspec/specs/component-tokens/spec.md + */ + public static function getBrandTokens(): array { + $tokens = array_merge( self::getLoginTokens(), self::getContentTokens(), self::getStatusTokens(), self::getTypographyTokens() ); - }//end getTokens() + + return array_map( + static fn (array $meta): array => array_merge($meta, ['group' => 'brand', 'primary' => false]), + $tokens + ); + }//end getBrandTokens() + + /** + * Returns the component layer, read from the shared mapping table. + * + * A missing or malformed table degrades to an empty component layer rather + * than an error: the brand tokens still edit, and the instance still renders, + * because `css/component-scopes.css` falls back to the captured global for + * every component token nobody set. + * + * @return array The component tokens. + * + * @spec openspec/specs/component-tokens/spec.md + */ + public static function getComponentTokens(): array { + if (self::$componentTokens !== null) { + return self::$componentTokens; + } + + self::$componentTokens = []; + + $raw = false; + if (is_file(self::COMPONENT_TOKENS_PATH) === true) { + $raw = file_get_contents(self::COMPONENT_TOKENS_PATH); + } + + if ($raw === false) { + return self::$componentTokens; + } + + $decoded = json_decode($raw, true); + if (is_array($decoded) === false || is_array($decoded['components'] ?? null) === false) { + return self::$componentTokens; + } + + foreach ($decoded['components'] as $componentId => $component) { + if (is_array($component['tokens'] ?? null) === false) { + continue; + } + + foreach ($component['tokens'] as $name => $meta) { + self::$componentTokens[$name] = [ + 'tab' => (string)($component['tab'] ?? 'content'), + 'type' => (string)($meta['type'] ?? 'text'), + 'label' => (string)($meta['label'] ?? $name), + 'group' => (string)$componentId, + 'primary' => (($meta['primary'] ?? false) === true), + // The Nextcloud variable this token replaces inside the + // component. A component token is deliberately undeclared + // until someone sets one, so `getComputedStyle` reports it + // as the empty string — the editor reads this global + // instead, and shows the colour the component is ACTUALLY + // wearing rather than a blank swatch. + 'global' => (string)($meta['global'] ?? ''), + ]; + } + } + + return self::$componentTokens; + }//end getComponentTokens() /** * Returns login and branding tab tokens. @@ -208,7 +327,7 @@ public static function isEditable(string $tokenName): bool { /** * Returns tokens grouped by tab. * - * @return array> Tokens grouped by tab id. + * @return array> Tokens grouped by tab id. * * @spec openspec/changes/retrofit-2026-05-24-annotate-nldesign/tasks.md#task-46 */ diff --git a/lib/Service/TokenRegistryInterface.php b/lib/Service/TokenRegistryInterface.php index a4994b38..622182ec 100644 --- a/lib/Service/TokenRegistryInterface.php +++ b/lib/Service/TokenRegistryInterface.php @@ -27,7 +27,11 @@ interface TokenRegistryInterface { /** * Returns the full registry of editable tokens. * - * @return array The token registry. + * `group` is `brand` for Nextcloud's own globals and the component id for a + * `--nldesign-component-*` token; `primary` marks a component token the brand + * primary used to drive, which the `primary_drives_components` setting locks. + * + * @return array The token registry. * * @spec openspec/changes/retrofit-2026-05-24-annotate-nldesign/tasks.md#task-45 */ diff --git a/lib/Service/TokenSetService.php b/lib/Service/TokenSetService.php index ea20729b..1ca91a91 100644 --- a/lib/Service/TokenSetService.php +++ b/lib/Service/TokenSetService.php @@ -234,6 +234,12 @@ public function getAvailableTokenSets(): array { // Read metadata for admin-uploaded custom sets from appconfig. $customMetadata = $this->readCustomManifest(); + // Nextcloud branding a set captured when it was saved (see + // BrandingCaptureService). It replaces the set's own theming block: + // it is what the admin had on when they saved this theme, which is + // what applying the theme is meant to bring back. + $captured = $this->readCapturedTheming(); + // Scan filesystem for actual CSS files. $tokenSets = []; if (is_dir($tokensDir) === true) { @@ -258,6 +264,10 @@ public function getAvailableTokenSets(): array { $tokenSet['theming'] = $meta['theming']; } + if (isset($captured[$id]) === true && is_array($captured[$id]) === true) { + $tokenSet['theming'] = $captured[$id]; + } + $tokenSet = $this->applyProvenance(tokenSet: $tokenSet, meta: ($meta ?? [])); $tokenSet = $this->applyWarnings( tokenSet: $tokenSet, @@ -278,6 +288,25 @@ public function getAvailableTokenSets(): array { return $tokenSets; }//end getAvailableTokenSets() + /** + * The Nextcloud branding each set captured when it was saved, by set id. + * + * @return array The captured theming blocks; empty when none are stored or the value is unreadable. + * + * @spec openspec/specs/token-sets/spec.md + */ + private function readCapturedTheming(): array { + $captured = json_decode( + (string)$this->config->getAppValue(Application::APP_ID, BrandingCaptureService::CAPTURED_KEY, '{}'), + true + ); + if (is_array($captured) === false) { + return []; + } + + return $captured; + }//end readCapturedTheming() + /** * Get the token sets an admin may select: `SELECTABLE_SHIPPED_SETS` plus * every admin-imported `custom-*` set. diff --git a/lib/Settings/Admin.php b/lib/Settings/Admin.php index 4b69d872..ed57c175 100644 --- a/lib/Settings/Admin.php +++ b/lib/Settings/Admin.php @@ -193,29 +193,26 @@ public function getForm(): TemplateResponse { 'nextcloud' ); - $hideSlogan = $this->config->getAppValue( - Application::APP_ID, - 'hide_slogan', - '0' - ) === '1'; - - $showMenuLabels = $this->config->getAppValue( - Application::APP_ID, - 'show_menu_labels', - '0' - ) === '1'; - - $darkVariantsEnabled = $this->config->getAppValue( - Application::APP_ID, - 'dark_variants', - '1' - ) === '1'; - - $marianneEnabled = $this->config->getAppValue( - Application::APP_ID, - 'marianne_enabled', - '0' - ) === '1'; + $hideSlogan = $this->isFlagOn(key: 'hide_slogan'); + $showMenuLabels = $this->isFlagOn(key: 'show_menu_labels'); + + // Whether the brand primary overrules every component token it used to + // drive. OFF by default, and that costs nothing visually: with no + // per-component value stored, the component tokens already resolve to the + // brand primary, so an instance that has never opened the playground + // renders identically either way. Turning it ON is the deliberate choice + // to give that back up — the primary wins again and the per-component + // colour controls lock. + $drivesComponents = $this->isFlagOn(key: 'primary_drives_components'); + + // Whether Save overrides asks first. Both default ON: the first save an + // admin makes is the one most worth a question, and the dialogs are the + // only place the "keep this as a new token set" choice is offered. + $confirmSaveStock = $this->isFlagOn(key: 'confirm_save_stock', fallback: '1'); + $confirmSaveTheme = $this->isFlagOn(key: 'confirm_save_theme', fallback: '1'); + + $darkVariantsEnabled = $this->isFlagOn(key: 'dark_variants', fallback: '1'); + $marianneEnabled = $this->isFlagOn(key: 'marianne_enabled'); // The design system backing the current token set — resolved from the // already-fetched $tokenSets inventory (TokenSetService surfaces @@ -245,6 +242,12 @@ public function getForm(): TemplateResponse { // reads them from. $this->initialState->provideInitialState('tokenSets', $tokenSets); $this->initialState->provideInitialState('currentTokenSet', $currentTokenSet); + + // The save-confirmation flags. Through initial state rather than the + // template because the two controls they drive are rendered by + // js/admin.js inside the token editor, which the template does not own. + $this->initialState->provideInitialState('confirmSaveStock', $confirmSaveStock); + $this->initialState->provideInitialState('confirmSaveTheme', $confirmSaveTheme); $this->publishPreviewState(activePreview: $activePreview, iconPackSource: $iconPackSource); $this->publishPlaygroundState( @@ -261,6 +264,7 @@ public function getForm(): TemplateResponse { 'currentDesignSystem' => $currentDesignSystem, 'hideSlogan' => $hideSlogan, 'showMenuLabels' => $showMenuLabels, + 'primaryDrivesComponents' => $drivesComponents, 'darkVariantsEnabled' => $darkVariantsEnabled, 'marianneEnabled' => $marianneEnabled, 'emailThemingState' => $emailThemingState, @@ -278,6 +282,24 @@ public function getForm(): TemplateResponse { ); }//end getForm() + /** + * Reads one on/off appconfig flag. + * + * Every toggle on this panel is stored the same way — the string '1' or + * '0' under the app id — and getForm() read five of them as five identical + * five-line blocks. One name for that shape keeps the method at the length + * phpmd allows and keeps the default visible at the call site, which is the + * part that differs between them. + * + * @param string $key The appconfig key. + * @param string $fallback The value to assume when the key was never set. + * + * @return bool Whether the flag is on. + */ + private function isFlagOn(string $key, string $fallback = '0'): bool { + return ($this->config->getAppValue(Application::APP_ID, $key, $fallback) === '1'); + }//end isFlagOn() + /** * Publish the two initial-state keys the preview banner and the icon-pack * indicator read. diff --git a/openspec/changes/apply-restore-earlier-version/.openspec.yaml b/openspec/changes/apply-restore-earlier-version/.openspec.yaml new file mode 100644 index 00000000..7f2ad572 --- /dev/null +++ b/openspec/changes/apply-restore-earlier-version/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-09-27 diff --git a/openspec/changes/apply-restore-earlier-version/design.md b/openspec/changes/apply-restore-earlier-version/design.md new file mode 100644 index 00000000..b6eee22e --- /dev/null +++ b/openspec/changes/apply-restore-earlier-version/design.md @@ -0,0 +1,47 @@ +# Design: restore an earlier version + +## Where it fits (development b4e7568) + +- `lib/Service/ThemingAuditService.php:189` `log()` is the single write path for theming changes; it is called from every controller that changes configuration (for example `lib/Controller/SettingsController.php:234`, `lib/Controller/OverridesController.php:154`, `lib/Controller/CustomTokenSetController.php:496`, `lib/Controller/ConfigBundleController.php:153`). It stores value summaries: CSS is reduced to a 12-character hash and a size (`openspec/specs/theming-audit/spec.md:6-19`), so the audit log alone cannot restore anything. +- `lib/Service/ConfigBundleService.php:241` `export()` serialises the complete instance-wide configuration; `:309` `import(array $bundle, bool $dryRun = false)` validates every section first and writes nothing on a hard failure. Font binaries are not in the bundle (`:50-57`). +- `lib/Controller/AuditController.php:82-96` exposes `list()` and `export()`, both `#[AuthorizedAdminSetting(Admin::class)]`; routes at `appinfo/routes.php:87-88`. +- `templates/settings/admin.php:553-579` renders the audit panel table (`#nldesign-audit-table`). + +## Decisions + +### 1. A version is a full bundle, taken after the change + +`ThemeVersionService::capture(string $auditAction)` stores `ConfigBundleService::export()` as `versions/.json` in the app's app data, with `id` a sortable timestamp plus a short random suffix. It runs from `ThemingAuditService::log()` after the entry is appended, and the entry gets a `versionId`. Taking the state after each change means the version list reads like the audit log: every row is "what the theme looked like after this". + +Rejected: replaying audit entries backwards. The entries hold hashes, not CSS, by design. + +Rejected: a snapshot before each change only. That needs a snapshot of the current state on first use and makes every row mean "before", which is harder to read next to the audit log. + +### 2. Capped like the audit log + +At most 50 versions and at most 20 MB in `versions/`, oldest removed first. A capture that fails (app data not writable) is logged as a warning and never fails the change, the same contract as the audit log. + +### 3. Restore goes through the bundle import + +Restore reads the version and calls `ConfigBundleService::import($bundle, dryRun: true)` to show the result, then `import($bundle)` on confirmation. No second write path exists, so every validator (custom token sets, overrides whitelist, token set ids, email footer) runs on a restore exactly as on an upload. A restore creates a new version itself, so it can be undone too. + +Fonts: the bundle carries font metadata only. If a version names a font that has since been deleted, the dry run lists it as "font no longer uploaded" and the restore leaves that role on the default font. + +### 4. Audit vocabulary gains one action + +`version_restored` with `old` the version that was active and `new` the restored version id. The `theming-audit` action vocabulary is closed, so the spec delta adds it explicitly. + +### 5. Endpoints and commands + +- `GET /settings/versions` (list: id, time, actor, audit action), `POST /settings/versions/{id}/preview` (dry run), `POST /settings/versions/{id}/restore`. All `#[AuthorizedAdminSetting(Admin::class)]`, CSRF-protected. +- `occ nldesign:config:versions` and `occ nldesign:config:restore [--dry-run]`, actor `cli` in the audit entry. + +## Risks + +- App data grows. The cap bounds it; the settings hint states the cap. +- A restore of a very old version can reintroduce a custom token set that was deleted on purpose. The dry run lists every custom set it will add, so the administrator sees that before confirming. + +## Out of scope + +- Restoring Nextcloud core theming values (logo, colours in the core Theming app). The bundle excludes them; the theming-sync dialog remains the path to re-apply them. +- Comparing two arbitrary versions side by side. diff --git a/openspec/changes/apply-restore-earlier-version/proposal.md b/openspec/changes/apply-restore-earlier-version/proposal.md new file mode 100644 index 00000000..72631241 --- /dev/null +++ b/openspec/changes/apply-restore-earlier-version/proposal.md @@ -0,0 +1,48 @@ +# Restore an earlier version of the theme + +## Why + +A theme change that goes wrong today can only be undone by hand. The audit log shows who changed what and when, but it stores hashes of the CSS, not the CSS itself, so nothing can be put back from it. An administrator who published a broken house style on a Friday afternoon has to remember every value, or restore a backup of the whole server. + +Restoring an earlier version is in the core of what thematiq does (applying and switching a theme). Four of the five competitors offer a partial form of it: Microsoft 365 through SharePoint file versions, openDesk through a Helm rollback, Liferay through reverting a publication, Tokens Studio through undo and immutable releases. + +#### Row `app-rollback-history` (thematiq matrix, area apply) + +- Capability: Roll back to an earlier version of the theme from a version history. +- Own rating: no; built.state `none`. Built evidence: lib/Controller/AuditController.php only exposes list() and export() (both #[AuthorizedAdminSetting]); lib/Service/ThemingAuditService.php has log()/getRecent()/exportAll() but no restore/revert method; grep -rniF 'rollback' in lib/ only turns up comments about the app-id migration rollback, not a theme-version rollback feature +- Microsoft 365 organisational branding (Entra company branding, Microsoft 365 themes, SharePoint brand center) rated `partial`: https://learn.microsoft.com/en-us/sharepoint/organization-assets-library ; https://learn.microsoft.com/en-us/sharepoint/brand-center-overview : brand assets live in ordinary SharePoint document libraries, which carry SharePoint's standard file version history, but the Entra sign-in and M365 org-theme settings themselves have no documented version history +- openDesk theming rated `partial`: opendesk@v1.18.2: the brand is Helm release values (theme.yaml.gotmpl), so reverting the values and redeploying (or helm rollback) restores it; opendesk-nextcloud-image@v2.17.2 functions.php:791-801 reapplies Nextcloud theming when commands change; docs/migrations.md:18 warns app-level rollbacks may need backups. Release history, not a theme history UI +- Liferay DXP (style books, themes, client extensions) rated `partial`: https://learn.liferay.com/w/dxp/sites/publishing-tools/publications/reverting-changes : published publications can be reverted from the History tab, covering page changes; style book entities are not in the Publications compatibility list and the editor offers only an undo History (https://liferay.atlassian.net/browse/LPD-101913), no version history of a style book +- Tokens Studio (Figma plugin and platform) rated `partial`: tokens-studio/figma-plugin@2.12.1 packages/tokens-studio-for-figma/src/app/components/NavbarUndoButton/NavbarUndoButton.tsx:31 undoes the last change; git sync branches keep history; Studio keeps immutable releases with per-release token diffs (https://documentation-v2.tokens.studio/releases/version-history.html) and the CLI can pin a source to an earlier release (`studio config set-ref`, https://documentation-v2.tokens.studio/cli/overview.html); no one-click restore of a live theme +- Rated no or unknown: Nextcloud Theming (built-in app) `no` + +The 2026-03-03 theme editor change listed "undoing individual previous saves (no history)" as a limit of that change. The theming-audit-log change later added the history this change restores from, so that limit no longer describes the product. + +## What changes + +- After every theming configuration change, thematiq keeps a version: the complete configuration bundle at that moment, stored in app data, capped by count and size. +- The theming audit log panel gets a restore action on every entry that has a version. +- Restoring shows what will change first, then applies the version through the existing validate-everything-first bundle import, and records a `version_restored` audit entry. +- `occ nldesign:config:versions` lists versions and `occ nldesign:config:restore ` restores one, for operators without the web interface. + +## Capabilities + +### New capabilities + +- `theme-versions`: keeping versions of the configuration and restoring one. + +### Modified capabilities + +- `theming-audit`: an audit entry names the version it produced, and the action vocabulary gains `version_restored`. + +## Impact + +- New `lib/Service/ThemeVersionService.php` (app data folder `versions`), called from `ThemingAuditService::log()`. +- `lib/Service/ConfigBundleService.php`: `export()` produces the snapshot, `import()` (with `dryRun`) restores it. +- `lib/Controller/AuditController.php`: version list, dry run and restore endpoints, all `#[AuthorizedAdminSetting]`. +- `templates/settings/admin.php` and `js/admin.js`: restore action in the audit panel and a confirmation dialog that shows the dry-run result. +- Two occ commands in `lib/Command/`. + +## Rows + +- `app-rollback-history` (thematiq matrix). diff --git a/openspec/changes/apply-restore-earlier-version/specs/theme-versions/spec.md b/openspec/changes/apply-restore-earlier-version/specs/theme-versions/spec.md new file mode 100644 index 00000000..5ef9d3c2 --- /dev/null +++ b/openspec/changes/apply-restore-earlier-version/specs/theme-versions/spec.md @@ -0,0 +1,92 @@ +# Spec delta: theme versions (theme-versions) + +A new capability. Every theming configuration change leaves a version behind, and an administrator can put any kept version back. + +## ADDED Requirements + +### Requirement: Every change leaves a version + +After every theming configuration change that writes an audit entry, the app MUST store the complete configuration bundle, as `ConfigBundleService::export()` produces it, as a version in the app's app data. The app MUST keep at most 50 versions and at most 20 MB of versions, removing the oldest first. A failure to store a version MUST be logged as a warning and MUST NOT fail or alter the change. + +#### Scenario: Switching the token set leaves a version + +- GIVEN an administrator on Settings > Administration > Theming with token set `rijkshuisstijl` active +- WHEN they switch the token set to `amsterdam` +- THEN a new version MUST exist whose bundle has `config.tokenSet` equal to `amsterdam` +- AND the audit entry for the switch MUST carry that version's id + +#### Scenario: A full app data folder does not block a change + +- GIVEN the app data folder cannot be written +- WHEN an administrator saves token overrides +- THEN the overrides MUST be saved +- AND the Nextcloud log MUST contain a warning that no version was kept + +### Requirement: An administrator restores a version from the audit log + +The theming audit log panel MUST offer a restore action on every entry that has a version. Choosing it MUST first show what the restore will change, from a dry run of the bundle import, and apply nothing until the administrator confirms. The restore MUST go through `ConfigBundleService::import()`, so every validator that guards an upload guards a restore. + +#### Scenario: An administrator restores the version from before a bad change + +- GIVEN an administrator who uploaded a custom token set that turned every button unreadable +- WHEN they open Settings > Administration > Theming, find the entry before the upload in the audit log and choose restore +- THEN a dialog MUST list the changes the restore will make, including the active token set going back +- AND after they confirm, the page MUST render in the restored token set +- AND the audit log MUST show a new entry with action `version_restored` + +#### Scenario: Cancelling the dialog changes nothing + +- GIVEN the restore dialog is open +- WHEN the administrator cancels it +- THEN the active configuration MUST be unchanged +- AND no audit entry MUST be written + +#### Scenario: A version that no longer validates is refused whole + +- GIVEN a version holding a custom token set that fails today's CSS validation whitelist +- WHEN the administrator restores it +- THEN the dry run MUST name the failing set +- AND confirming MUST write nothing + +### Requirement: A restore can itself be undone + +A restore MUST store a new version like any other change, so the configuration from before the restore stays in the list. + +#### Scenario: Undoing a restore + +- GIVEN an administrator restored an older version +- WHEN they open the audit log +- THEN the version from just before the restore MUST be listed with a restore action + +### Requirement: Versions are admin-only + +`GET /settings/versions`, `POST /settings/versions/{id}/preview` and `POST /settings/versions/{id}/restore` MUST carry `#[AuthorizedAdminSetting(OCA\Thematiq\Settings\Admin::class)]` and MUST be CSRF-protected. An unknown version id MUST answer 404. + +#### Scenario: A non-admin cannot list or restore versions + +- GIVEN a signed-in user who is not an administrator and has no delegated theming setting +- WHEN they call `POST /apps/thematiq/settings/versions//restore` +- THEN the response MUST be 403 +- AND the configuration MUST be unchanged + +### Requirement: Operators restore from the command line + +`occ nldesign:config:versions` MUST list the kept versions with id, time, actor and audit action. `occ nldesign:config:restore ` MUST restore one, with `--dry-run` printing the changes without writing, and the audit entry MUST carry actor `cli`. + +#### Scenario: An operator restores on a server without the web interface + +- GIVEN an operator with shell access to the server +- WHEN they run `occ nldesign:config:restore --dry-run` and then without `--dry-run` +- THEN the first run MUST print the changes and write nothing +- AND the second run MUST apply them and exit 0 + +### Requirement: Fonts that are gone are reported, not invented + +A version names custom fonts by metadata only. When a version names a font that is no longer uploaded, the dry run MUST list it as no longer uploaded, and the restore MUST leave that font role on the default font. + +#### Scenario: Restoring a version whose heading font was deleted + +- GIVEN a version whose heading font was deleted after the version was kept +- WHEN the administrator previews the restore +- THEN the dialog MUST say the heading font is no longer uploaded +- AND after the restore, headings MUST render in the default font diff --git a/openspec/changes/apply-restore-earlier-version/specs/theming-audit/spec.md b/openspec/changes/apply-restore-earlier-version/specs/theming-audit/spec.md new file mode 100644 index 00000000..0541ea5c --- /dev/null +++ b/openspec/changes/apply-restore-earlier-version/specs/theming-audit/spec.md @@ -0,0 +1,26 @@ +# Spec delta: theming audit (theming-audit) + +Audit entries point at the version they produced, and restoring a version is an audited action. + +## ADDED Requirements + +### Requirement: An audit entry names its version + +Every audit entry written by `ThemingAuditService::log()` for which a version was stored MUST carry a `versionId` field with that version's id. An entry for which no version could be stored MUST omit the field rather than carry an empty value. + +#### Scenario: The panel knows which rows can be restored + +- GIVEN three audit entries, the second written while app data was not writable +- WHEN an administrator calls `GET /apps/thematiq/settings/audit?limit=3` +- THEN the first and third entries MUST carry a `versionId` +- AND the second MUST NOT carry the field + +### Requirement: Restoring a version is an audited action + +The closed action vocabulary MUST include `version_restored`. Its entry MUST carry `old` equal to the version id that was active before the restore and `new` equal to the restored version id, and the actor rules of every other entry (`cli` for occ, the user id otherwise). + +#### Scenario: A restore from the command line is recorded + +- GIVEN an operator runs `occ nldesign:config:restore ` +- WHEN the restore completes +- THEN the audit log MUST contain one `version_restored` entry with actor `cli` and `new` equal to `` diff --git a/openspec/changes/apply-restore-earlier-version/tasks.md b/openspec/changes/apply-restore-earlier-version/tasks.md new file mode 100644 index 00000000..19f79b94 --- /dev/null +++ b/openspec/changes/apply-restore-earlier-version/tasks.md @@ -0,0 +1,25 @@ +# Tasks: restore an earlier version + +Tick a box when the work is merged to `development`. + +## 1. Versions + +- [ ] 1.1 Add `lib/Service/ThemeVersionService.php` (`capture()`, `list()`, `get()`, cap at 50 versions and 20 MB). Verify: `tests/Unit/Service/ThemeVersionServiceTest.php` covers capture, ordering, the count cap, the size cap and a failing app data folder. +- [ ] 1.2 Call `capture()` from `ThemingAuditService::log()` and add `versionId` to the entry. Verify: `ThemingAuditServiceTest` asserts the entry carries the id and that a failing capture still writes the entry. + +## 2. Restore + +- [ ] 2.1 Version list, preview and restore endpoints on `AuditController`, routes in `appinfo/routes.php`. Verify: `tests/Unit/Controller/AuditControllerTest.php` for the three endpoints, including a non-admin getting 403 and a restore of an unknown id getting 404. +- [ ] 2.2 Restore through `ConfigBundleService::import()` (dry run first), audit action `version_restored`. Verify: unit test that a restore writes exactly one `version_restored` entry and captures a new version. +- [ ] 2.3 `occ nldesign:config:versions` and `occ nldesign:config:restore [--dry-run]`. Verify: command tests in `tests/Unit/Command/`. + +## 3. Settings page + +- [ ] 3.1 Restore action per audit row that has a version, confirmation dialog listing the dry-run changes. Verify: Playwright scenario "An administrator restores the version from before a bad change". +- [ ] 3.2 Keyboard: the restore button and dialog are reachable and closable by keyboard, focus returns to the row. Verify: Playwright keyboard run. + +## 4. Quality + +- [ ] 4.1 l10n: new strings in `l10n/en.json` and `l10n/nl.json`. Verify: `npm run test:l10n`. +- [ ] 4.2 Docs: a "Restore an earlier version" section in `docs/`. Verify: the docs build. +- [ ] 4.3 Test a restore with an incomplete custom token set and with dark mode on. Verify: manual check recorded in the PR. diff --git a/openspec/changes/apply-scheduled-theme-switch/.openspec.yaml b/openspec/changes/apply-scheduled-theme-switch/.openspec.yaml new file mode 100644 index 00000000..7f2ad572 --- /dev/null +++ b/openspec/changes/apply-scheduled-theme-switch/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-09-27 diff --git a/openspec/changes/apply-scheduled-theme-switch/design.md b/openspec/changes/apply-scheduled-theme-switch/design.md new file mode 100644 index 00000000..12f4eff5 --- /dev/null +++ b/openspec/changes/apply-scheduled-theme-switch/design.md @@ -0,0 +1,43 @@ +# Design: scheduled theme switch + +## Where it fits (development b4e7568) + +- `lib/Controller/SettingsController.php:226-240` `setTokenSet()` checks `TokenSetService::isValidTokenSet()` (`:227`), writes app config `token_set` (`:232`) and logs `token_set_changed` (`:234`). +- `lib/BackgroundJob/UpstreamFreshnessJob.php:38-89` is the one `TimedJob` in the app (`setInterval()` at `:70`, `TIME_INSENSITIVE` at `:71`), registered in `appinfo/info.xml:132-134`. +- `templates/settings/admin.php:66-76` renders the token set dropdown. +- `openspec/specs/config-portability/spec.md:6-20`: every new instance-wide value joins the bundle with a `bundleVersion` bump. +- `openspec/specs/theme-preview/spec.md:145` "Publish Runs The Existing Instance-Wide Dialogs": an interactive switch offers the theming-sync dialog for core logo and colours. + +## Decisions + +### 1. One stored list in app config + +`scheduled_switches` holds a JSON list of `{id, tokenSet, startAt, endAt|null, syncCoreTheming, createdBy, createdAt}` with times in UTC ISO 8601. The list is short (a handful of planned looks), so app config fits, like `group_token_sets`. Overlapping windows are refused on save: one planned look at a time is what people plan, and two overlapping windows have no obvious winner. + +### 2. A time-sensitive job every five minutes + +`ScheduledSwitchJob` runs every 300 seconds and is time sensitive. It applies a switch whose `startAt` has passed, records the set it replaced as `revertTo` on the entry, and at `endAt` switches back to `revertTo`. A switch that is due while the set it names no longer exists is skipped, logged, audited as failed and left in the list marked `failed`, so the administrator sees it. + +Rejected: cron entries per switch. Nextcloud apps have no per-date job API beyond jobs checking a date, and one job keeps the logic testable. + +### 3. The same validation, no dialog + +The job calls the same token set path as `setTokenSet()` (extracted to a service method both use), so the same `isValidTokenSet()` check guards it. Nobody is there to answer the theming-sync dialog, so core logo and colours are only synced when the administrator ticked `syncCoreTheming` when planning, using the set's theming metadata the dialog would have offered. + +### 4. Audited as its own action + +`scheduled_switch_applied` with `old` and `new` token set ids, actor `system`, and the schedule id in context. A failed switch writes the same action with `new` null and the reason. + +### 5. In the bundle + +`config.scheduledSwitches` joins the bundle with a `bundleVersion` bump. Import validates every entry (set exists, windows do not overlap, times parse) in phase 1 like every other section. + +## Risks + +- Background jobs that do not run (AJAX cron on a quiet instance) delay a switch. The settings block shows the time the job last ran and warns when cron mode is AJAX. +- Time zones: times are entered in the administrator's browser time zone and stored in UTC; the list shows both. + +## Out of scope + +- Scheduling other settings than the token set (fonts, overrides). A later change can widen the entry. +- Per-group schedules. diff --git a/openspec/changes/apply-scheduled-theme-switch/proposal.md b/openspec/changes/apply-scheduled-theme-switch/proposal.md new file mode 100644 index 00000000..74697e44 --- /dev/null +++ b/openspec/changes/apply-scheduled-theme-switch/proposal.md @@ -0,0 +1,41 @@ +# Schedule a theme switch for a date + +## Why + +Communication teams plan their looks ahead: a campaign week, King's Day, a remembrance period with a quiet palette. Today an administrator has to be at a keyboard at midnight to switch the token set, and again to switch it back. Switching a theme is the core of thematiq (applying and switching a theme), and it cannot be planned. + +#### Row `app-scheduled-switch` (thematiq matrix, area apply) + +- Capability: Schedule a theme change for a date, such as a campaign or a holiday look. +- Own rating: no; built.state `none`. Built evidence: grep -rniF 'schedule' across lib/, js/admin.js, templates/ found only js/admin.js:2761 schedulePreviewRepaint() (an animation-frame debounce, unrelated) and UpstreamFreshnessJob's own cron wiring comment; no date-based or calendar theme-change feature exists anywhere +- Liferay DXP (style books, themes, client extensions) rated `partial`: https://learn.liferay.com/w/dxp/sites/publishing-tools/publications/making-and-publishing-changes : publications can be scheduled for a date and time and content pages are supported, so a page style book selection can go live on schedule; style books themselves are not in the compatibility list (https://learn.liferay.com/w/dxp/sites/publishing-tools/publications) and Publications is in maintenance mode as of 2026.Q1 +- Rated no or unknown: Nextcloud Theming (built-in app) `no`, Microsoft 365 organisational branding (Entra company branding, Microsoft 365 themes, SharePoint brand center) `unknown`, openDesk theming `no`, Tokens Studio (Figma plugin and platform) `no` + +## What changes + +- An administrator schedules a switch to a token set at a date and time, with an optional end date that switches back to the set that was active before. +- A background job applies due switches through the same validation as the token set dropdown and writes an audit entry for each one. +- The theming settings page lists planned switches, lets the administrator cancel one, and says which set is active until when. +- Planned switches travel in the configuration bundle, so a campaign prepared on acceptance goes to production with the rest. + +## Capabilities + +### New capabilities + +- `scheduled-switch`: planning, applying and cancelling timed token set switches. + +### Modified capabilities + +- `config-portability`: the bundle carries planned switches. +- `theming-audit`: the action vocabulary gains `scheduled_switch_applied`. + +## Impact + +- New `lib/Service/ScheduledSwitchService.php` and `lib/BackgroundJob/ScheduledSwitchJob.php` (registered in `appinfo/info.xml` next to `UpstreamFreshnessJob`). +- `lib/Controller/SettingsController.php`: list, create and cancel endpoints next to `setTokenSet()` (`:226`). +- `templates/settings/admin.php` and `js/admin.js`: a "Planned switches" block under the token set dropdown (`:66-76`). +- `lib/Service/ConfigBundleService.php`: one new bundle key, `bundleVersion` bump. + +## Rows + +- `app-scheduled-switch` (thematiq matrix). diff --git a/openspec/changes/apply-scheduled-theme-switch/specs/config-portability/spec.md b/openspec/changes/apply-scheduled-theme-switch/specs/config-portability/spec.md new file mode 100644 index 00000000..7af985c4 --- /dev/null +++ b/openspec/changes/apply-scheduled-theme-switch/specs/config-portability/spec.md @@ -0,0 +1,22 @@ +# Spec delta: configuration portability (config-portability) + +Planned switches are instance-wide configuration, so they travel in the bundle. + +## ADDED Requirements + +### Requirement: Planned switches travel in the bundle + +The bundle MUST carry the planned switches as `config.scheduledSwitches`, with their token set, start, end and core sync option, and the change that adds them MUST bump `bundleVersion`. Import MUST validate every planned switch in phase 1 (token set exists in the bundle or on the target, windows do not overlap, times parse) and MUST write nothing when one fails. Runtime state (`revertTo`, `failed`) MUST NOT be exported. + +#### Scenario: A campaign prepared on acceptance goes to production + +- GIVEN an acceptance server with a planned switch to `koningsdag-oranje` and that custom set uploaded +- WHEN an administrator exports the bundle there and imports it on production +- THEN production MUST list the same planned switch +- AND production MUST hold the custom set it names + +#### Scenario: An overlapping plan blocks the whole import + +- GIVEN a bundle whose planned switches overlap +- WHEN an administrator imports it with `occ nldesign:config:import` +- THEN the command MUST exit non-zero, name the overlap and write nothing diff --git a/openspec/changes/apply-scheduled-theme-switch/specs/scheduled-switch/spec.md b/openspec/changes/apply-scheduled-theme-switch/specs/scheduled-switch/spec.md new file mode 100644 index 00000000..aa55706d --- /dev/null +++ b/openspec/changes/apply-scheduled-theme-switch/specs/scheduled-switch/spec.md @@ -0,0 +1,79 @@ +# Spec delta: scheduled switch (scheduled-switch) + +A new capability. An administrator plans a token set switch for a date, optionally with an end date, and the app applies it on time. + +## ADDED Requirements + +### Requirement: An administrator plans a switch + +Settings > Administration > Theming MUST let an administrator plan a switch to any available token set at a start date and time, with an optional end date and time. The app MUST refuse a planned switch whose token set does not exist, whose end is not after its start, or whose window overlaps another planned switch, and MUST say why. + +#### Scenario: An administrator plans a campaign look + +- GIVEN an administrator on Settings > Administration > Theming with `rijkshuisstijl` active +- WHEN they plan a switch to `koningsdag-oranje` from 26 April 18:00 to 28 April 08:00 and save +- THEN the "Planned switches" list MUST show the switch with both times in their own time zone +- AND the active token set MUST still be `rijkshuisstijl` + +#### Scenario: Overlapping plans are refused + +- GIVEN a planned switch from 1 May to 7 May +- WHEN an administrator plans a second switch from 5 May to 10 May +- THEN the save MUST fail with a message that the windows overlap +- AND the list MUST still hold only the first switch + +### Requirement: The app applies a due switch and switches back + +A background job MUST run at least every five minutes and apply every planned switch whose start has passed, through the same token set validation as the dropdown. When the switch has an end, the app MUST switch back to the token set that was active just before the switch, once the end has passed. The switch MUST reach every page on the next page load, like a switch from the dropdown. + +#### Scenario: The look starts and ends on time + +- GIVEN a planned switch to `koningsdag-oranje` from 26 April 18:00 to 28 April 08:00, and `rijkshuisstijl` active +- WHEN the background job runs at 26 April 18:03 +- THEN a user who opens the dashboard MUST see the `koningsdag-oranje` colours +- AND when the job runs at 28 April 08:02, the dashboard MUST show `rijkshuisstijl` again + +#### Scenario: A switch to a deleted set fails visibly + +- GIVEN a planned switch to the custom set `custom-campagne`, which an administrator deleted afterwards +- WHEN the switch becomes due +- THEN the active token set MUST stay unchanged +- AND the planned switch MUST show as failed in the list with the reason +- AND the audit log MUST contain a `scheduled_switch_applied` entry with `new` empty and the reason + +### Requirement: Core theming is synced only when asked + +A planned switch MUST NOT change Nextcloud core theming values (logo, primary colour, background) unless the administrator ticked "also update the Nextcloud logo and colours" when planning it. When ticked, the app MUST apply the theming metadata of the token set that the theming-sync dialog would have offered. + +#### Scenario: A switch without core sync keeps the core logo + +- GIVEN a planned switch without the core sync option +- WHEN the switch is applied +- THEN the Nextcloud core logo and primary colour MUST be unchanged + +### Requirement: An administrator cancels a planned switch + +An administrator MUST be able to cancel a planned switch that has not started, and a running switch, which then switches back at once. The endpoints MUST carry `#[AuthorizedAdminSetting(OCA\Thematiq\Settings\Admin::class)]`. + +#### Scenario: Cancelling a running campaign + +- GIVEN a running switch to `koningsdag-oranje` that replaced `rijkshuisstijl` +- WHEN an administrator cancels it on Settings > Administration > Theming +- THEN the active token set MUST be `rijkshuisstijl` +- AND the switch MUST disappear from the list + +#### Scenario: A non-admin cannot plan a switch + +- GIVEN a signed-in user who is not an administrator +- WHEN they call `POST /apps/thematiq/settings/scheduled-switches` +- THEN the response MUST be 403 + +### Requirement: The page warns when switches may run late + +The "Planned switches" block MUST show when the scheduling job last ran, and MUST warn when Nextcloud's background job mode is AJAX, because planned switches then only run while someone uses the site. + +#### Scenario: An administrator on AJAX cron is warned + +- GIVEN a server whose background jobs run in AJAX mode +- WHEN an administrator opens the "Planned switches" block +- THEN the block MUST state that switches may start late and name the cron setting to change diff --git a/openspec/changes/apply-scheduled-theme-switch/specs/theming-audit/spec.md b/openspec/changes/apply-scheduled-theme-switch/specs/theming-audit/spec.md new file mode 100644 index 00000000..f979f074 --- /dev/null +++ b/openspec/changes/apply-scheduled-theme-switch/specs/theming-audit/spec.md @@ -0,0 +1,15 @@ +# Spec delta: theming audit (theming-audit) + +Applying a planned switch is an audited action. + +## ADDED Requirements + +### Requirement: Applied planned switches are audited + +The closed action vocabulary MUST include `scheduled_switch_applied`. Its entry MUST carry actor `system`, `old` and `new` token set ids, and the planned switch id in its context. A planned switch that could not be applied MUST be audited with `new` empty and the reason. + +#### Scenario: A start and an end leave two entries + +- GIVEN a planned switch with a start and an end +- WHEN both have passed and the job has run +- THEN the audit log MUST contain two `scheduled_switch_applied` entries with actor `system`, the first to the planned set and the second back diff --git a/openspec/changes/apply-scheduled-theme-switch/tasks.md b/openspec/changes/apply-scheduled-theme-switch/tasks.md new file mode 100644 index 00000000..aed5a465 --- /dev/null +++ b/openspec/changes/apply-scheduled-theme-switch/tasks.md @@ -0,0 +1,25 @@ +# Tasks: scheduled theme switch + +Tick a box when the work is merged to `development`. + +## 1. Service and job + +- [ ] 1.1 Extract the token set write from `SettingsController::setTokenSet()` into a service method used by both the controller and the job. Verify: existing `SettingsControllerTest` stays green. +- [ ] 1.2 `lib/Service/ScheduledSwitchService.php`: create (with overlap and existence checks), list, cancel, due-switch resolution. Verify: `tests/Unit/Service/ScheduledSwitchServiceTest.php` with a fixed `ITimeFactory`. +- [ ] 1.3 `lib/BackgroundJob/ScheduledSwitchJob.php` (300 s, time sensitive), registered in `appinfo/info.xml`. Verify: unit test applies a due switch, reverts at `endAt`, and marks a switch to a deleted set as `failed`. +- [ ] 1.4 Audit action `scheduled_switch_applied`. Verify: `ThemingAuditServiceTest` accepts the action; job test asserts one entry per switch. + +## 2. Endpoints and settings page + +- [ ] 2.1 `GET/POST /settings/scheduled-switches`, `DELETE /settings/scheduled-switches/{id}`, all `#[AuthorizedAdminSetting]`. Verify: controller unit tests, including overlap refusal (400) and non-admin (403). +- [ ] 2.2 "Planned switches" block under the token set dropdown: set, start, optional end, sync core theming checkbox, list with cancel. Verify: Playwright scenario "An administrator plans a campaign look". + +## 3. Bundle + +- [ ] 3.1 `config.scheduledSwitches` in `ConfigBundleService::export()` and `import()`, `bundleVersion` bump. Verify: `ConfigBundleServiceTest` round trip and a refused import with an overlapping entry. + +## 4. Quality + +- [ ] 4.1 l10n en and nl for all new strings. Verify: `npm run test:l10n`. +- [ ] 4.2 Docs: "Plan a theme switch" in `docs/`, including the cron requirement. Verify: the docs build. +- [ ] 4.3 Test a switch to an incomplete token set and in dark mode. Verify: manual check recorded in the PR. diff --git a/openspec/changes/authoring-dtcg-export/.openspec.yaml b/openspec/changes/authoring-dtcg-export/.openspec.yaml new file mode 100644 index 00000000..7f2ad572 --- /dev/null +++ b/openspec/changes/authoring-dtcg-export/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-09-27 diff --git a/openspec/changes/authoring-dtcg-export/design.md b/openspec/changes/authoring-dtcg-export/design.md new file mode 100644 index 00000000..6cd76864 --- /dev/null +++ b/openspec/changes/authoring-dtcg-export/design.md @@ -0,0 +1,149 @@ +# Design: write design tokens in the DTCG format + +## Where it fits (development `b4e7568`) + +- **Reading DTCG** is one class. `DesignTokensMapper::map()` (`lib/Service/DesignTokensMapper.php:156-185`) + flattens the tree, resolves aliases and serialises each type. Colours go through + `serializeColor()` (`:587-597`) and `serializeColorObject()` (`:610-633`). The accepted colour + spaces are `SRGB_COLOR_SPACES` (`:57`). `componentsToHex()` (`:644-652`) scales each of three + components by 255. `resolveTarget()` (`:799-812`) maps a dotted path to a `--nldesign-*` name by + the longest suffix in `MAPPING` (`:107-120`), twelve entries. Every `$` key, `$extensions` + included, is skipped while walking (`:228-230`). +- **Downloading a set** is one method. `CustomTokenSetController::export()` + (`lib/Controller/CustomTokenSetController.php:560-572`) returns the served CSS of a custom set as + `text/css`. It refuses ids it cannot read. Shipped sets have no download. The token editor's + "Download" (`js/admin.js:2470-2472`, `exportOverrides()` at `:2875`) exports the overrides CSS + only. +- **Colour consumers read sRGB.** `ContrastService::parseColor()` (`lib/Service/ContrastService.php:242-274`) + reads `#rgb`, `#rrggbb` and `rgb()`/`rgba()` only. `DarkPaletteService::deriveDarkDeclarations()` + (`lib/Service/DarkPaletteService.php:289-325`) skips any token `parseColor()` cannot read + (`:316-321`). Nextcloud core theming takes a hex primary colour. +- **The JS runtime refuses DTCG.** `js/lib/tokenConverter.js:2047-2055` throws a 422 that sends + DTCG to the server. That stays: the server is the one DTCG authority. +- **The set's own declarations** are read with `CssParserService::parseRootBlock()`, as + `CustomTokenSetController::mapFromCss()` does (`:429`). `DarkPaletteService::resolveLightDeclarations()` + (`:259-275`) shows the layering with `defaults.css`, which the export deliberately does not do + (decision 2). + +## Decision 1: one writer, the mirror of the mapper + +A new `DesignTokensWriter` turns a declaration map into a DTCG document. It lives beside +`DesignTokensMapper` and shares its knowledge through one new helper, `ColorSpaceConverter`, which +both classes use. + +Rejected: writing DTCG in `js/lib/tokenConverter.js` and offering it in the browser. The JS runtime +refuses DTCG on purpose, and two DTCG implementations is the drift `nlds-theme-converter` exists to +prevent. + +## Decision 2: export what the set declares, not the defaults under it + +The export holds the custom properties of `css/tokens/{id}.css`, nothing more. Layering +`defaults.css` under it would freeze today's defaults into every exported brand. The overrides from +the token editor are not included either: they belong to the instance, not to the set. + +Rejected: exporting the resolved set with defaults. It is the right answer for a reference page +(the lane change `catalogue-token-reference-pages`), not for interchange. + +## Decision 3: token paths and the thematiq extension + +A CSS name becomes a path by its first two segments. `--nldesign-color-primary-hover` becomes +`nldesign` > `color` > `primary-hover`. `--utrecht-button-background-color` becomes `utrecht` > +`button` > `background-color`. The name stays readable in any tool, and the twelve suffixes in +`MAPPING` still match (`nldesign.color.primary-hover` ends with `color.primary-hover`). + +Every token also carries `"$extensions": {"nl.conduction.thematiq": {"cssVariable": "--nldesign-color-primary-hover"}}`. +The DTCG format names extensions by reverse domain. On import, `resolveTarget()` first reads that +extension. When it names a custom property the validator accepts, that is the target. Otherwise the +suffix table decides, as today. This is what makes the round trip exact: the suffix table has twelve +entries for eight distinct targets, and the converter's semantic layer alone requires 26 tokens +(`openspec/changes/nlds-theme-converter/proposal.md`). + +Rejected: a longer suffix table. It would still miss component and palette tokens, and every new +semantic token would need a second entry. + +## Decision 4: typing each value + +The writer gives each value a DTCG type by its shape. The name helps only where the shape is +ambiguous. + +| CSS value | DTCG type | `$value` | +|---|---|---| +| `#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`, `rgb()`, `rgba()`, `hsl()`, `hsla()`, a named colour | `color` | object in `srgb`, with `hex` | +| `oklch()`, `oklab()`, `lab()`, `lch()`, `hwb()`, `color( ...)` | `color` | object in that colour space, with an sRGB `hex` fallback | +| number with `px` or `rem` | `dimension` | `{value, unit}` | +| number with `ms` or `s` | `duration` | `{value, unit}` | +| `cubic-bezier(a, b, c, d)` | `cubicBezier` | `[a, b, c, d]` | +| a font stack, name contains `font-family` | `fontFamily` | array of family names | +| 1 to 1000, name contains `font-weight` | `fontWeight` | number | +| a plain number | `number` | number | +| `var(--x)` where `--x` is in the set | same type as `--x` | alias `{group.path}` | + +Anything else has no DTCG type: `em`, `%`, `vh`, gradients, `url()`, keywords, `color-mix()`, +`light-dark()`. Those go into `$extensions["nl.conduction.thematiq"].cssOnly` at the document root +as `{name: value}`. A DTCG tool ignores that block. Thematiq's import reads it back through the +validator. + +Task 1.1 checks the table against the DTCG format and colour module text (v2025.10) before code. +In particular: that `dimension` allows only `px` and `rem`, the `duration` object form, and the +list of colour space identifiers. + +Rejected: writing a token without `$type` for untyped values. The document would be invalid DTCG, +and the mapper refuses such tokens with `missing-type` by its own spec. + +## Decision 5: colours outside sRGB + +On **export**, a CSS Color 4 function keeps its colour space. `oklch(0.5 0.1 250)` is written as +`{"colorSpace": "oklch", "components": [0.5, 0.1, 250], "hex": "#32669a"}`. The `hex` is the sRGB +conversion, clipped when out of gamut, as the DTCG fallback. + +On **import**, a colour in any DTCG colour space is converted to sRGB and stored as hex: + +- in gamut: the converted value; +- out of gamut, with a `hex` in the document: that `hex`, reported `adapted` with + `out-of-gamut-hex-fallback`; +- out of gamut, no `hex`: each channel clipped to 0..1, reported `adapted` with + `out-of-gamut-clipped` and the original value. + +The stored value is sRGB because every consumer reads sRGB: `ContrastService::parseColor()`, the +dark palette, and Nextcloud core theming. Storing `oklch()` in the set would silently switch off +contrast warnings and dark derivation for that token (`DarkPaletteService.php:316-321`). + +Rejected: teaching every consumer CSS Color 4 now. It is the better end state and a far larger +change. The report keeps the loss visible until then. + +`srgb-linear` components get the sRGB transfer function before scaling. `alpha` below 1 is kept as +an 8-digit hex value. `ContrastService` learning 8-digit hex is owned by the change +`authoring-token-value-types`. Until it lands, a translucent colour shows as "not evaluated", which +is honest. + +## Decision 6: endpoint and buttons + +`GET /apps/thematiq/settings/tokensets/{id}/dtcg` with `#[AuthorizedAdminSetting(Admin::class)]`. +It accepts any id `TokenSetService::isValidTokenSet()` accepts, and 404s otherwise. It returns +`application/json` as `{id}.tokens.json` with `Content-Disposition: attachment`. The document root +carries `$description` with the set name and `$extensions["nl.conduction.thematiq"]` with the set +id, the app version and the `cssOnly` block. + +The panel gets two buttons. "Download as design tokens" next to the token set dropdown exports the +selected set. The same action sits next to "Download" in each custom set row. + +No audit entry: a download changes no configuration. + +## Risks + +- **Colour maths drift between PHP and the reference.** Conversions follow the CSS Color 4 sample + code. Tests pin one value per colour space, each channel within 1 of the reference. +- **Other tools read extensions differently.** Tokens Studio reads `$extensions` it knows and keeps + the rest. A tool that drops unknown extensions loses the CSS names, and the import falls back to + the suffix table. The round-trip guarantee holds only for thematiq to thematiq. +- **Palette and component names on import.** They pass through the converter's naming rules, so a + set exported as `amsterdam` and imported as "Test" renames palette steps to `--test-*`. The + `--nldesign-*` layer is exact. + +## Out of scope + +- Dark values in the export. How a token carries a dark value is decided in + `authoring-token-value-types`. The export follows that change. +- Bulk conversion between formats (row `aut-dtcg-bulk-convert`). +- A public, non-admin DTCG feed. +- Exporting several brands as one Tokens Studio file (see `authoring-multi-brand-token-source`). diff --git a/openspec/changes/authoring-dtcg-export/proposal.md b/openspec/changes/authoring-dtcg-export/proposal.md new file mode 100644 index 00000000..2c355efd --- /dev/null +++ b/openspec/changes/authoring-dtcg-export/proposal.md @@ -0,0 +1,101 @@ +# Write design tokens in the DTCG format + +## Why + +A government house style lives in more than one tool. The design team keeps it in Tokens Studio or +another DTCG tool, a supplier builds a portal with Style Dictionary, and the Nextcloud instance runs +it through thematiq. Those tools exchange tokens in the W3C Design Tokens Community Group (DTCG) +format. Thematiq reads that format and cannot write it. + +Two halves are missing: + +1. **Writing DTCG.** The only download of a set is its served CSS. + `CustomTokenSetController::export()` (`lib/Controller/CustomTokenSetController.php:561-572`) + returns `text/css`, and only for custom sets. A shipped set cannot be downloaded at all from the + panel. A designer who changes a colour in thematiq's token editor has no way to carry it back. +2. **Colour spaces other than sRGB.** `DesignTokensMapper` accepts only `srgb` and `srgb-linear` + (`lib/Service/DesignTokensMapper.php:57`). Every other colour space is skipped as + `unsupported-color-space` (`:611-614`). A brand colour defined in `oklch` or `display-p3` is lost. + +Reading the sRGB half also has two faults that a writer would expose on the first round trip: + +- `srgb-linear` components are written as if they were gamma-encoded sRGB. + `componentsToHex()` (`:644-652`) multiplies each component by 255 without the sRGB transfer + function. A linear grey of 0.5 becomes `#808080`. The correct sRGB value is `#bcbcbc`. +- The colour object's `alpha` is never read. `componentsToHex()` writes three channels only, so a + translucent brand overlay imports as an opaque colour. + +Matrix evidence (quoted from `rowblock.py aut-dtcg-colour-objects`): + +### Row `aut-dtcg-colour-objects` (thematiq matrix, area sync) + +- Capability: Read and write tokens in the stable DTCG specification, including colour values + stored as objects with an explicit colour space. +- Own rating: partial; built.state `built`. Built evidence: lib/Service/DesignTokensMapper.php:605-630 + serializeColorObject() reads DTCG v2025.10 colour objects (hex or components) in the sRGB family + and reports other colour spaces as unsupported-color-space (48-56); writing DTCG is not supported + (aut-dtcg-bulk-convert) +- Demand: featureRequest at https://github.com/tokens-studio/figma-plugin/issues/3236 + (interchange with other government token tools depends on the stable DTCG colour format) +- Tokens Studio (Figma plugin and platform) rated `partial`: tokens-studio/figma-plugin@2.12.1 + packages/tokens-studio-for-figma/src/utils/color/gradientTokenToCss.ts:7-15 reads + {colorSpace, components} colour objects only inside gradients; colour tokens are strings; related + board post https://feedback.tokens.studio/p/dtcg-format-update-on-color-tokens-and-support-for-color + (12 votes, Discussion) +- Rated no or unknown: Nextcloud Theming (built-in app) `no`, Microsoft 365 organisational + branding (Entra company branding, Microsoft 365 themes, SharePoint brand center) `no`, openDesk + theming `no`, Liferay DXP (style books, themes, client extensions) `no` + +The row is partial. Reading DTCG colour objects in the sRGB family is built. Writing DTCG and every +other colour space are missing. + +## What changes + +- An administrator can download any token set, shipped or custom, as a DTCG document. The button + sits next to the token set dropdown and next to each custom set. +- Every custom property in the set becomes a token. Colours are written as DTCG colour objects + with a colour space, components, alpha when below 1, and an sRGB `hex` fallback. +- A colour written in a CSS Color 4 function (`oklch()`, `lab()`, `color(display-p3 ...)`) keeps + its own colour space in the export. +- Values that have no DTCG type (a gradient, `em`, a keyword) are kept in a thematiq extension at + the document root. The document stays valid DTCG, and a thematiq round trip loses nothing. +- Every token carries its CSS name in the `nl.conduction.thematiq` extension. On import, thematiq + maps a token with that extension straight to its CSS name, so an exported set imports back to the + same `--nldesign-*` values. +- Import accepts every colour space in the DTCG colour module. A colour outside sRGB is converted + to sRGB, because contrast checks, dark variants and Nextcloud core theming all read sRGB. A colour + outside the sRGB gamut uses the document's `hex` fallback, or is clipped and reported as adapted. +- Import applies the sRGB transfer function to `srgb-linear` components and keeps `alpha` as an + 8-digit hex value. + +## Capabilities + +### New capabilities + +- `token-set-dtcg-export`: writing a token set as a DTCG document, the value typing rules, the + thematiq extension and the round-trip guarantee. + +### Modified capabilities + +- `custom-token-sets` (`openspec/specs/custom-token-sets/spec.md`): the requirement "W3C Design + Tokens JSON Import" changes in two places. Every DTCG colour space is accepted and converted to + sRGB. The `nl.conduction.thematiq` extension is read instead of ignored. The rest of the + requirement is unchanged. + +## Impact + +- **Code**: new `lib/Service/DesignTokensWriter.php`; new `lib/Service/ColorSpaceConverter.php` + (CSS Color 4 conversions to and from sRGB, gamut check), used by the writer and the mapper; + `lib/Service/DesignTokensMapper.php` (`serializeColorObject()`, `componentsToHex()`, the extension + lookup in `resolveTarget()`); `lib/Controller/CustomTokenSetController.php` (new `exportDtcg()`); + `appinfo/routes.php`; `templates/settings/admin.php` and `js/admin.js` (two download buttons); + `l10n/en.json`, `l10n/nl.json`; `docs/features/import-export.md`. +- **Endpoint**: new `GET /apps/thematiq/settings/tokensets/{id}/dtcg`, admin-only. The existing + CSS export stays as it is. +- **Sibling repos**: none. `js/lib/tokenConverter.js` keeps refusing DTCG input and pointing at the + server (`:2047-2055`), which stays the one DTCG authority. +- **Related row**: `aut-dtcg-bulk-convert` (the evidence names it) is not part of this change. + +## Rows + +- `aut-dtcg-colour-objects`, from the thematiq matrix (`openspec/parity/capabilities.json`). diff --git a/openspec/changes/authoring-dtcg-export/specs/custom-token-sets/spec.md b/openspec/changes/authoring-dtcg-export/specs/custom-token-sets/spec.md new file mode 100644 index 00000000..b5da5fb7 --- /dev/null +++ b/openspec/changes/authoring-dtcg-export/specs/custom-token-sets/spec.md @@ -0,0 +1,152 @@ +# Spec delta: custom token sets (authoring-dtcg-export) + +The DTCG import changes in two places: every DTCG colour space is accepted and converted to sRGB, +and thematiq's own extension is read instead of ignored. The requirement title stays as it is in +`openspec/specs/custom-token-sets/spec.md`, because a modified requirement is matched by its title. +The em-dashes of the old text are replaced by colons and commas. + +## MODIFIED Requirements + +### Requirement: W3C Design Tokens JSON Import + +The upload control MUST also accept a JSON file in the W3C DTCG Design Tokens Format Module +v2025.10. The importer MUST implement the format's core semantics, not a lookalike subset: + +- **`$type` resolution with group inheritance**: a token's type is its own `$type`, else the + `$type` of the nearest ancestor group declaring one. Tokens whose resolved type is absent MUST + be skipped with reason `missing-type` (never guessed from the value shape). +- **Typed `$value` handling** for at least: `color` (legacy string literals `#rrggbb`/`rgb()` + AND the v2025.10 object form in every colour space the DTCG colour module lists; see the colour + rules below), `dimension` (`{value, unit}` object serialized as ``; legacy string + form accepted), `fontFamily` (string or array serialized as a quoted CSS font stack), + `fontWeight` (number, or v2025.10 weight keyword normalized to its numeric value), and + **composite `typography`** (each sub-property, `fontFamily`, `fontSize`, `fontWeight`, + `lineHeight`, mapped individually where a corresponding `--nldesign-*` target exists, unmapped + sub-properties counted as skipped). +- **Colour objects**: a colour in `srgb` MUST use its `hex` when present, else its components. A + colour in `srgb-linear` MUST apply the sRGB transfer function to its components before scaling + to 0..255. A colour in any other colour space MUST be converted to sRGB with the CSS Color 4 + conversions. A converted colour outside the sRGB gamut MUST use the document's `hex` when + present and be reported `adapted` with reason `out-of-gamut-hex-fallback`, else each channel + MUST be clipped to 0..1 and reported `adapted` with reason `out-of-gamut-clipped` and the + original value. An `alpha` below 1 MUST be kept as an 8-digit hex value `#rrggbbaa`. A colour + space the colour module does not list MUST be skipped with reason `unsupported-color-space`. +- **`$extensions`** MUST be ignored without error and without affecting mapping + (passthrough-ignore), with one exception: a token whose + `$extensions["nl.conduction.thematiq"].cssVariable` names a custom property MUST be mapped to + that name before the suffix table is tried, and the document root's + `$extensions["nl.conduction.thematiq"].cssOnly` map MUST be read as declarations. Both still pass + the validator. **`$deprecated`** (boolean `true` or string form) on an imported token MUST + surface a warning naming the token path (and the string message when given) while still + importing the value. +- Recognized tokens MUST be mapped to `--nldesign-*` variables via the published mapping table; + unmapped tokens MUST be skipped and counted with reason `unmapped-path`. The mapped result + MUST pass through the same whitelist, serialization, and storage pipeline as CSS uploads + (`CustomTokenSetValidator`; atomic write to `css/tokens/custom-*.css`). DTCG hardening + changes what the mapper understands, never where or how sets are stored. + +#### Scenario: DTCG color tokens map onto the nldesign vocabulary + +@e2e exclude mapping logic, PHPUnit on the mapper with DTCG fixtures +- GIVEN a `huisstijl.tokens.json` containing `{ "color": { "primary": { "$type": "color", "$value": "#154273" }, "on-primary": { "$type": "color", "$value": "#ffffff" } } }` +- WHEN the admin uploads it with display name "Eigen huisstijl" +- THEN `css/tokens/custom-eigen-huisstijl.css` MUST contain `--nldesign-color-primary: #154273` and `--nldesign-color-primary-text: #ffffff` +- AND the response MUST report `imported: 2, skipped: 0` + +#### Scenario: Group-level $type is inherited by descendant tokens + +@e2e exclude mapping logic, PHPUnit on the mapper +- GIVEN a document `{ "color": { "$type": "color", "primary": { "$value": "#154273" } } }` + where the token itself declares no `$type` +- WHEN the document is imported +- THEN `color.primary` MUST resolve type `color` from its group +- AND MUST be imported as `--nldesign-color-primary: #154273` + +#### Scenario: v2025.10 object color and dimension values serialize to CSS + +@e2e exclude mapping logic, PHPUnit on the mapper +- GIVEN a `color.primary` token whose `$value` is the object form with an sRGB color space and + hex fallback for `#154273`, and a `dimension.border-radius` token with + `$value: { "value": 8, "unit": "px" }` +- WHEN the document is imported +- THEN the emitted declarations MUST be `--nldesign-color-primary: #154273` and + `--nldesign-border-radius: 8px` +- AND a color token in a colour space the DTCG colour module does not list MUST be skipped with + reason `unsupported-color-space` and its path listed + +#### Scenario: An oklch brand colour is converted to sRGB + +@e2e exclude mapping logic, PHPUnit on the mapper and ColorSpaceConverter +- GIVEN an administrator on Settings > Administration > Theming +- AND a `color.primary` token `{ "colorSpace": "oklch", "components": [0.5, 0.1, 250] }` +- WHEN the administrator uploads the document +- THEN `--nldesign-color-primary` MUST be `#32669a`, each channel within 1 of that value +- AND the token MUST count as imported, not skipped + +#### Scenario: A display-p3 colour outside sRGB is clipped and reported + +@e2e exclude mapping logic, PHPUnit on the mapper +- GIVEN a `color.primary` token `{ "colorSpace": "display-p3", "components": [1, 0, 0] }` with no `hex` +- WHEN the document is imported +- THEN `--nldesign-color-primary` MUST be `#ff0000` +- AND the report MUST list the token as `adapted` with reason `out-of-gamut-clipped` and the original value + +#### Scenario: A linear sRGB grey keeps its lightness + +@e2e exclude mapping logic, PHPUnit on the mapper +- GIVEN a `color.background` token `{ "colorSpace": "srgb-linear", "components": [0.5, 0.5, 0.5] }` with no `hex` +- WHEN the document is imported +- THEN `--nldesign-color-background` MUST be `#bcbcbc` +- AND it MUST NOT be `#808080` + +#### Scenario: A translucent colour keeps its alpha + +@e2e exclude mapping logic, PHPUnit on the mapper +- GIVEN a `color.primary-light` token `{ "colorSpace": "srgb", "components": [0.0824, 0.2588, 0.451], "alpha": 0.5 }` +- WHEN the document is imported +- THEN `--nldesign-color-primary-light` MUST be `#15427380` + +#### Scenario: The thematiq extension names the target + +@e2e exclude mapping logic, PHPUnit on the mapper +- GIVEN a token at `nldesign.color.error` carrying + `"$extensions": { "nl.conduction.thematiq": { "cssVariable": "--nldesign-color-error" } }` +- WHEN the document is imported +- THEN `--nldesign-color-error` MUST receive the token's value +- AND the token MUST NOT be skipped as `unmapped-path` + +#### Scenario: Composite typography token maps sub-values individually + +@e2e exclude mapping logic, PHPUnit on the mapper +- GIVEN a `typography.font-family`-adjacent composite token of `$type: "typography"` whose + `$value.fontFamily` is `["Fira Sans", "sans-serif"]` +- WHEN the document is imported +- THEN `--nldesign-font-family` MUST be emitted as the serialized font stack +- AND composite sub-properties without an `--nldesign-*` target MUST be counted as skipped, each + with its sub-path + +#### Scenario: Unmapped DTCG tokens degrade to skipped counts + +@e2e exclude mapping tolerance, PHPUnit on the mapper +- GIVEN a DTCG file containing a recognized `color.primary` token and an unrecognized + `shadow.elevation-1` token +- WHEN the admin uploads it +- THEN the upload MUST succeed with `imported: 1, skipped: 1` +- AND the skipped token path MUST be listed in the response with reason `unmapped-path` + +#### Scenario: Deprecated token imports with a surfaced warning + +@e2e exclude mapping logic, PHPUnit on the mapper +- GIVEN a mapped token carrying `"$deprecated": "Use color.brand.primary instead"` +- WHEN the document is imported +- THEN the token MUST still be imported +- AND the response `warnings` MUST contain the token path and the deprecation message +- AND the admin panel MUST display the warning after upload + +#### Scenario: Malformed JSON is rejected + +@e2e exclude parse guard, PHPUnit on the controller +- GIVEN a `.json` upload that is not valid JSON +- WHEN the admin uploads it +- THEN the upload MUST be rejected with HTTP 422 and a localized parse error +- AND no file MUST be written diff --git a/openspec/changes/authoring-dtcg-export/specs/token-set-dtcg-export/spec.md b/openspec/changes/authoring-dtcg-export/specs/token-set-dtcg-export/spec.md new file mode 100644 index 00000000..7d69aa42 --- /dev/null +++ b/openspec/changes/authoring-dtcg-export/specs/token-set-dtcg-export/spec.md @@ -0,0 +1,140 @@ +# Spec delta: token set DTCG export (authoring-dtcg-export) + +An administrator can download any token set as a W3C Design Tokens (DTCG) document, carry it to +another tool, and import it back into thematiq without losing a value. + +## ADDED Requirements + +### Requirement: Any token set can be downloaded as a DTCG document + +`GET /apps/thematiq/settings/tokensets/{id}/dtcg` MUST return the named set as a DTCG v2025.10 +document for every id `TokenSetService::isValidTokenSet()` accepts, shipped or custom. The response +MUST be `application/json` with `Content-Disposition: attachment; filename="{id}.tokens.json"`. An +unknown id MUST return 404. The endpoint MUST be admin-only and MUST write no audit entry. The +settings panel MUST offer "Download as design tokens" next to the token set dropdown, for the +selected set, and in each custom set row. + +#### Scenario: An administrator downloads a shipped set +- GIVEN an administrator on Settings > Administration > Theming +- AND "Gemeente Amsterdam" is selected in the token set dropdown +- WHEN the administrator clicks "Download as design tokens" +- THEN the browser MUST download `amsterdam.tokens.json` +- AND the file MUST parse as JSON with at least one token carrying `$type` and `$value` + +#### Scenario: An administrator downloads a custom set from its row +- GIVEN the custom set "Gemeente Voorbeeld" exists +- WHEN the administrator clicks "Download as design tokens" in its row +- THEN the browser MUST download `custom-gemeente-voorbeeld.tokens.json` + +#### Scenario: An unknown set is refused +@e2e exclude API error path, covered by the Newman collection +- GIVEN no set with the id `does-not-exist` +- WHEN an administrator requests `GET /apps/thematiq/settings/tokensets/does-not-exist/dtcg` +- THEN the response MUST be 404 with a generic message + +#### Scenario: A non-administrator cannot download +@e2e exclude Auth posture, covered by the Newman collection +- GIVEN a logged-in user who is not an administrator +- WHEN they request `GET /apps/thematiq/settings/tokensets/amsterdam/dtcg` +- THEN the response MUST be 403 + +### Requirement: The export holds what the set declares + +The document MUST hold one token per custom property declared in `css/tokens/{id}.css`, and +nothing from `defaults.css` or from the token editor overrides. A token's path MUST be the CSS +name's first segment, then its second segment, then the rest of the name joined with dashes. Every +token MUST carry `$extensions["nl.conduction.thematiq"].cssVariable` with its CSS name. The root +MUST carry `$description` with the set name, and `$extensions["nl.conduction.thematiq"]` with the +set id and the app version. + +#### Scenario: A semantic token gets a readable path and its CSS name +@e2e exclude Document shape, covered by PHPUnit on DesignTokensWriter +- GIVEN a set declaring `--nldesign-color-primary-hover: #0f3059` +- WHEN it is exported +- THEN the token MUST sit at `nldesign` > `color` > `primary-hover` +- AND its `cssVariable` extension MUST be `--nldesign-color-primary-hover` + +#### Scenario: Defaults and overrides are left out +@e2e exclude Document shape, covered by PHPUnit on DesignTokensWriter +- GIVEN a set that does not declare `--nldesign-border-radius` +- AND the token editor overrides `--color-primary-element` on this instance +- WHEN the set is exported +- THEN the document MUST NOT contain a token for either name + +### Requirement: Each value gets a DTCG type by its shape + +The writer MUST type each value by its shape: colours as `color`, `px` and `rem` as `dimension`, +`ms` and `s` as `duration`, `cubic-bezier()` as `cubicBezier`, a font stack in a `font-family` +token as `fontFamily`, 1 to 1000 in a `font-weight` token as `fontWeight`, and a plain number as +`number`. A `var(--x)` whose target is in the set MUST become a DTCG alias to the target's path. A +value with no DTCG type MUST NOT become a token. It MUST be written to the root map +`$extensions["nl.conduction.thematiq"].cssOnly` as `{name: value}`. + +#### Scenario: A duration and an easing become motion tokens +@e2e exclude Typing rules, covered by PHPUnit on DesignTokensWriter +- GIVEN a set declaring `--nldesign-animation-quick: 100ms` (as `css/tokens/vng.css:187` does) and + `--nldesign-animation-easing: cubic-bezier(0.2, 0, 0, 1)` +- WHEN it is exported +- THEN the first MUST be `$type: "duration"` with `$value: { "value": 100, "unit": "ms" }` +- AND the second MUST be `$type: "cubicBezier"` with `$value: [0.2, 0, 0, 1]` + +#### Scenario: An alias stays an alias +@e2e exclude Typing rules, covered by PHPUnit on DesignTokensWriter +- GIVEN a set declaring `--nldesign-color-link: var(--nldesign-color-primary)` and `--nldesign-color-primary: #154273` +- WHEN it is exported +- THEN the link token's `$value` MUST be `{nldesign.color.primary}` + +#### Scenario: A gradient is kept outside the tokens +@e2e exclude Typing rules, covered by PHPUnit on DesignTokensWriter +- GIVEN a set declaring `--nldesign-header-background: linear-gradient(90deg, #154273, #01689b)` +- WHEN it is exported +- THEN no token MUST exist for it +- AND the root `cssOnly` map MUST hold that name with that exact value + +### Requirement: Colours are written as colour objects with a colour space + +A colour MUST be written as a DTCG colour object with `colorSpace`, `components`, and an sRGB `hex` +fallback. `alpha` MUST be present when it is below 1. A hex, `rgb()`, `hsl()` or named colour MUST +be written in `srgb`. A colour in `oklch()`, `oklab()`, `lab()`, `lch()`, `hwb()` or +`color( ...)` MUST keep its own colour space, and its `hex` MUST be the clipped sRGB +conversion. Every colour object written MUST be accepted by the importer. + +#### Scenario: A hex brand colour becomes an sRGB object +@e2e exclude Colour serialisation, covered by PHPUnit on DesignTokensWriter +- GIVEN a set declaring `--nldesign-color-primary: #154273` +- WHEN it is exported +- THEN the token's `$value` MUST be `{ "colorSpace": "srgb", "components": [0.0824, 0.2588, 0.451], "hex": "#154273" }`, components rounded to four decimals +- AND it MUST have no `alpha` key + +#### Scenario: An oklch colour keeps its colour space +@e2e exclude Colour serialisation, covered by PHPUnit on DesignTokensWriter and ColorSpaceConverter +- GIVEN a set declaring `--nldesign-color-primary: oklch(0.5 0.1 250)` +- WHEN it is exported +- THEN the token's `colorSpace` MUST be `oklch` with components `[0.5, 0.1, 250]` +- AND its `hex` MUST be `#32669a`, each channel within 1 of that value + +#### Scenario: A translucent colour carries its alpha +@e2e exclude Colour serialisation, covered by PHPUnit on DesignTokensWriter +- GIVEN a set declaring `--nldesign-color-primary-light: #15427380` +- WHEN it is exported +- THEN the token's `alpha` MUST be `0.502`, rounded to three decimals +- AND its `hex` MUST be `#154273` + +### Requirement: A thematiq round trip is exact + +Importing an exported set into thematiq under a new name MUST give every `--nldesign-*` token of +the original the same value, compared after lowercasing hex and expanding 3-digit hex. Every +`cssOnly` entry with an `--nldesign-*` name MUST come back unchanged. Brand palette and component +names MAY be renamed by the converter's naming rules. + +#### Scenario: An administrator carries a set out and back in +- GIVEN an administrator downloads "Gemeente Amsterdam" as design tokens +- WHEN the administrator uploads that file as a new custom set named "Amsterdam kopie" +- THEN every `--nldesign-*` declaration of `css/tokens/custom-amsterdam-kopie.css` MUST equal the one in `css/tokens/amsterdam.css` +- AND the upload report MUST list no `--nldesign-*` token as skipped + +#### Scenario: Every shipped set survives the round trip +@e2e exclude Fleet check over every shipped file, covered by PHPUnit DesignTokensRoundTripTest +- GIVEN every set in `token-sets.json` +- WHEN each is exported and imported again in the test +- THEN each `--nldesign-*` value MUST match its original diff --git a/openspec/changes/authoring-dtcg-export/tasks.md b/openspec/changes/authoring-dtcg-export/tasks.md new file mode 100644 index 00000000..cfb740bf --- /dev/null +++ b/openspec/changes/authoring-dtcg-export/tasks.md @@ -0,0 +1,89 @@ +# Tasks: write design tokens in the DTCG format + +Tick a box when the work is merged to `development`, not when it is started. + +## 1. Check the inputs before building + +- [ ] 1.1 Read the DTCG Format Module and Color Module v2025.10. Confirm the colour space + identifiers, the `dimension` units (`px`, `rem`), the `duration` object form, the + `cubicBezier` shape and the extension naming rule that design decision 4 assumes. Record the + section numbers in design.md, or correct the table. Verify: the citations are in design.md. +- [ ] 1.2 Write `tests/Unit/fixtures/dtcg/colour-spaces.tokens.json` with one colour per colour + space, each with the sRGB value the CSS Color 4 sample code gives. Verify: the file parses, + and each expected value is written next to its source in a comment field. + +## 2. Colour conversion + +- [ ] 2.1 Add `lib/Service/ColorSpaceConverter.php`: to and from sRGB for every listed colour space, + a gamut check, and clipping. Verify: PHPUnit `ColorSpaceConverterTest` pins one value per + colour space within 1 per channel, including `oklch(0.5 0.1 250)` to `#32669a` and + `display-p3` `[1, 0, 0]` to `#ff0000` out of gamut. +- [ ] 2.2 Use it in `DesignTokensMapper::serializeColorObject()`: every listed colour space, the + transfer function for `srgb-linear`, `alpha` as 8-digit hex, the two `out-of-gamut-*` report + reasons. Verify: PHPUnit `DesignTokensMapperTest::testOklchConvertsToSrgb`, + `testSrgbLinearAppliesTransferFunction`, `testAlphaKeptAsEightDigitHex`, + `testOutOfGamutUsesHexFallback`, `testOutOfGamutWithoutHexIsClipped`, and every existing + mapper test stays green. +- [ ] 2.3 Carry the two new reason codes into the converter report and into the panel's reason + labels (`reasonLabel()` in `js/admin.js`). Verify: `npm run test:unit` on + `tests/vitest/admin-dtcg-diagnostics.spec.js` with a clipped colour. + +## 3. The thematiq extension on import + +- [ ] 3.1 Read `$extensions["nl.conduction.thematiq"].cssVariable` in `resolveTarget()` before the + suffix table, and the root `cssOnly` map as declarations. Both pass the validator. Verify: + PHPUnit `testExtensionNamesTheTarget`, `testCssOnlyMapIsImported`, + `testExtensionWithForbiddenValueIsRefused`. + +## 4. The writer and the endpoint + +- [ ] 4.1 Add `lib/Service/DesignTokensWriter.php`: paths, the extension on every token, the typing + table, aliases, the `cssOnly` map, the root description (design decisions 3 and 4). Verify: + PHPUnit `DesignTokensWriterTest` with one case per row of the typing table. +- [ ] 4.2 Add `CustomTokenSetController::exportDtcg()` on + `GET /settings/tokensets/{id}/dtcg` with `#[AuthorizedAdminSetting(Admin::class)]`, 404 for an + unknown id, `application/json` and the `{id}.tokens.json` attachment name. Verify: PHPUnit + `CustomTokenSetControllerTest::testExportDtcgShippedSet`, `testExportDtcgUnknownIs404`, and + `curl -u admin:$ADMIN_PASSWORD -OJ http://localhost:8080/apps/thematiq/settings/tokensets/amsterdam/dtcg`. +- [ ] 4.3 Add the 200, 403 and 404 calls to `tests/integration/thematiq.postman_collection.json`, + credentials from environment variables. Verify: `tests/integration/run-newman.sh` is green. +- [ ] 4.4 Add `tests/Unit/Service/DesignTokensRoundTripTest.php`: export every shipped set, import + it through `TokenSetConverterService::convert()`, and compare every `--nldesign-*` value. + Verify: the test is green for all sets in `token-sets.json`. + +## 5. Admin panel + +- [ ] 5.1 Add "Download as design tokens" next to the token set dropdown (`templates/settings/admin.php:66-76`) + for the selected set, and next to "Download" in each custom set row (`js/admin.js:4381-4392`). + Verify: Playwright `tests/e2e/spec-coverage/token-set-dtcg-export.spec.ts`, scenarios "An + administrator downloads a shipped set" and "An administrator downloads a custom set from its row". +- [ ] 5.2 Round trip by hand in the browser: download a set, upload it under a new name, compare. + Verify: Playwright scenario "An administrator carries a set out and back in". + +## 6. Mandatory categories (config.yaml, ADR-005, 009, 010, 011) + +- [ ] 6.1 Localisation: the button labels, the two new reason labels and every new error text in + `l10n/en.json` and `l10n/nl.json`, domain `thematiq`. Verify: `npm run test:l10n`. +- [ ] 6.2 Documentation: a "Download as design tokens" section in `docs/features/import-export.md` + with a screenshot, the colour space rules and the round-trip limits. Verify: `npm run build` + in `docs/`. +- [ ] 6.3 WCAG AA contrast: a colour converted from another colour space reaches the contrast check + as sRGB hex. Test an `oklch` primary that fails 4.5:1 on white and gets a warning. Verify: + PHPUnit `CustomTokenSetServiceTest::testConvertedColourIsContrastChecked`. +- [ ] 6.4 Dark mode: a set imported from `oklch` gets a dark variant for that token. Verify: PHPUnit + `DarkPaletteServiceTest::testConvertedColourIsDerived`. +- [ ] 6.5 Incomplete token sets: exporting a set that declares only a few tokens gives only those + tokens, and importing it back falls back to the defaults for the rest. Verify: PHPUnit + `DesignTokensRoundTripTest::testIncompleteSetStaysIncomplete`. +- [ ] 6.6 Accessibility: both buttons are real `