diff --git a/QUICKSTART.md b/QUICKSTART.md
index 10984ed..7b4176b 100644
--- a/QUICKSTART.md
+++ b/QUICKSTART.md
@@ -31,24 +31,45 @@ Or via web UI: Settings β Apps β search "nldesign" β Enable
4. Select your organization (Rijkshuisstijl, Utrecht, etc.)
5. Reload the page
-π **Done!** Your Nextcloud now uses Dutch government design styling with professional Fira Sans typography.
+### 5. Set Background Color (Important!)
+
+The NL Design app does not set a background color automatically. You must configure it in Nextcloud's theming:
+
+1. Stay in: **Settings β Administration β Theming** (Nextcloud's main section)
+2. Scroll to: **Background and color** section
+3. Click on **Color** and enter the background color for your token set:
+
+| Token Set | Primary Color | Background Color |
+|-----------|--------------|------------------|
+| **Rijkshuisstijl** | `#154273` (blue) | `#F5F6F7` (light gray) |
+| **Utrecht** | `#CC0000` (red) | `#FFFFFF` (white) |
+| **Amsterdam** | `#EC0000` (red) | `#FFFFFF` (white) |
+| **Den Haag** | `#1A7A3E` (green) | `#FFFFFF` (white) |
+| **Rotterdam** | `#00811F` (green) | `#FFFFFF` (white) |
+
+4. **Click on Background image** β Select **Remove background image**
+5. Save changes
+
+**Note**: Primary colors are set automatically by NL Design when you select a token set. Only the background color needs manual configuration.
+
+π **Done!** Your Nextcloud now uses Dutch government design styling with professional Fira Sans typography and correct colors.
## π¨ Token Sets Available
-| Set | Color | Best For |
-|-----|-------|----------|
-| **Rijkshuisstijl** | Blue (#154273) | National government |
-| **Utrecht** | Red (#cc0000) | Municipality |
-| **Amsterdam** | Red (#ec0000) | Municipality |
-| **Den Haag** | Green (#1a7a3e) | Municipality |
-| **Rotterdam** | Green (#00811f) | Municipality |
+| Token Set | Primary Color | Background | Best For |
+|-----------|--------------|------------|----------|
+| **Rijkshuisstijl** | `#154273` (blue) | `#F5F6F7` | National government |
+| **Utrecht** | `#CC0000` (red) | `#FFFFFF` | Municipality |
+| **Amsterdam** | `#EC0000` (red) | `#FFFFFF` | Municipality |
+| **Den Haag** | `#1A7A3E` (green) | `#FFFFFF` | Municipality |
+| **Rotterdam** | `#00811F` (green) | `#FFFFFF` | Municipality |
## β
What You Get
- β
Professional Fira Sans typography
- β
Official Dutch government colors
- β
Sharp corners (Rijkshuisstijl) or rounded (municipalities)
-- β
Clean white backgrounds
+- β
Configurable background colors via Nextcloud theming
- β
WCAG AA accessible
- β
Responsive design
- β
No build required
@@ -98,9 +119,13 @@ docker exec -u 33 nextcloud php occ maintenance:repair
### Colors Wrong?
-1. Check which token set is selected
-2. Hard reload browser (Ctrl+Shift+R)
-3. Clear Nextcloud cache
+1. Check which token set is selected in NL Design settings
+2. **Check background color** in Nextcloud Theming settings:
+ - Should be `#F5F6F7` for Rijkshuisstijl
+ - Should be `#FFFFFF` for other token sets
+ - Background image should be removed
+3. Hard reload browser (Ctrl+Shift+R)
+4. Clear Nextcloud cache
## π Full Documentation
diff --git a/README.md b/README.md
index 7a80038..ce6dd9f 100644
--- a/README.md
+++ b/README.md
@@ -72,6 +72,37 @@ Navigate to **Settings β Administration β Theming** and find the "NL Design
Select your preferred design token set and reload the page to see the changes.
+### Configuring Background Color
+
+The NL Design app does not set a background color - this allows you to use Nextcloud's built-in theming system to configure the background color to match your organization's needs.
+
+**To set the background color:**
+
+1. Navigate to **Settings β Administration β Theming** (Nextcloud's main theming section, not the NL Design section)
+2. Scroll to **Background and color** section
+3. Click on **Color** and enter your desired background color
+4. **Important**: Also click on **Background image** and select **Remove background image** to ensure a solid color background
+
+**Recommended colors by token set:**
+
+| Token Set | Primary Color | Background Color |
+|-----------|--------------|------------------|
+| **Rijkshuisstijl** | `#154273` (donkerblauw) | `#F5F6F7` (light gray) |
+| **Utrecht** | `#CC0000` (red) | `#FFFFFF` (white) |
+| **Amsterdam** | `#EC0000` (red) | `#FFFFFF` (white) |
+| **Den Haag** | `#1A7A3E` (green) | `#FFFFFF` (white) |
+| **Rotterdam** | `#00811F` (green) | `#FFFFFF` (white) |
+
+**Note**: The primary colors are automatically applied by the NL Design app when you select a token set. You only need to configure the background color manually in Nextcloud's theming settings.
+
+**Why does NL Design not set the background color?**
+
+By delegating background color management to Nextcloud's theming system, organizations can:
+- Use their own custom background colors
+- Easily change backgrounds without modifying app code
+- Maintain compatibility with Nextcloud's theming API
+- Allow different backgrounds for different user groups or instances
+
## Fonts
This app uses **Fira Sans** as an open-source alternative to the proprietary government fonts:
diff --git a/appinfo/info.xml b/appinfo/info.xml
index 4aeeecb..b8fdbc1 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -16,7 +16,7 @@ Supported design token sets:
Configure which token set to use in the admin settings.
]]>
- 0.1.1-unstable.2
+ 0.1.1-unstable.4
agpl
Conduction
NLDesign
diff --git a/appinfo/routes.php b/appinfo/routes.php
index 54232a0..87ce0cc 100644
--- a/appinfo/routes.php
+++ b/appinfo/routes.php
@@ -6,5 +6,7 @@
'routes' => [
['name' => 'settings#setTokenSet', 'url' => '/settings/tokenset', 'verb' => 'POST'],
['name' => 'settings#getTokenSet', 'url' => '/settings/tokenset', 'verb' => 'GET'],
+ ['name' => 'settings#setSloganSetting', 'url' => '/settings/slogan', 'verb' => 'POST'],
+ ['name' => 'settings#setMenuLabelsSetting', 'url' => '/settings/menulabels', 'verb' => 'POST'],
],
];
diff --git a/composer.lock b/composer.lock
new file mode 100644
index 0000000..3da8d67
--- /dev/null
+++ b/composer.lock
@@ -0,0 +1,501 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "4c1f12c66945c04972b5f6f99b70a032",
+ "packages": [],
+ "packages-dev": [
+ {
+ "name": "kubawerlos/php-cs-fixer-custom-fixers",
+ "version": "v3.36.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/kubawerlos/php-cs-fixer-custom-fixers.git",
+ "reference": "e1f97f6463f0b2a22e0dd320948a04132ff9c501"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/kubawerlos/php-cs-fixer-custom-fixers/zipball/e1f97f6463f0b2a22e0dd320948a04132ff9c501",
+ "reference": "e1f97f6463f0b2a22e0dd320948a04132ff9c501",
+ "shasum": ""
+ },
+ "require": {
+ "ext-filter": "*",
+ "ext-tokenizer": "*",
+ "friendsofphp/php-cs-fixer": "^3.87",
+ "php": "^7.4 || ^8.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.6.24 || ^10.5.51 || ^11.5.44"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "PhpCsFixerCustomFixers\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Kuba WerΕos",
+ "email": "werlos@gmail.com"
+ }
+ ],
+ "description": "A set of custom fixers for PHP CS Fixer",
+ "support": {
+ "issues": "https://github.com/kubawerlos/php-cs-fixer-custom-fixers/issues",
+ "source": "https://github.com/kubawerlos/php-cs-fixer-custom-fixers/tree/v3.36.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/kubawerlos",
+ "type": "github"
+ }
+ ],
+ "time": "2026-01-31T07:02:11+00:00"
+ },
+ {
+ "name": "nextcloud/coding-standard",
+ "version": "v1.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nextcloud/coding-standard.git",
+ "reference": "8e06808c1423e9208d63d1bd205b9a38bd400011"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nextcloud/coding-standard/zipball/8e06808c1423e9208d63d1bd205b9a38bd400011",
+ "reference": "8e06808c1423e9208d63d1bd205b9a38bd400011",
+ "shasum": ""
+ },
+ "require": {
+ "kubawerlos/php-cs-fixer-custom-fixers": "^3.22",
+ "php": "^8.0",
+ "php-cs-fixer/shim": "^3.17"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Nextcloud\\CodingStandard\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Christoph Wurst",
+ "email": "christoph@winzerhof-wurst.at"
+ }
+ ],
+ "description": "Nextcloud coding standards for the php cs fixer",
+ "keywords": [
+ "dev"
+ ],
+ "support": {
+ "issues": "https://github.com/nextcloud/coding-standard/issues",
+ "source": "https://github.com/nextcloud/coding-standard/tree/v1.4.0"
+ },
+ "time": "2025-06-19T12:27:27+00:00"
+ },
+ {
+ "name": "nextcloud/ocp",
+ "version": "v31.0.9",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nextcloud-deps/ocp.git",
+ "reference": "abd32429d794ede1d92b7b0a88a1070371c907b5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nextcloud-deps/ocp/zipball/abd32429d794ede1d92b7b0a88a1070371c907b5",
+ "reference": "abd32429d794ede1d92b7b0a88a1070371c907b5",
+ "shasum": ""
+ },
+ "require": {
+ "php": "~8.1 || ~8.2 || ~8.3 || ~8.4",
+ "psr/clock": "^1.0",
+ "psr/container": "^2.0.2",
+ "psr/event-dispatcher": "^1.0",
+ "psr/log": "^3.0.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-stable31": "31.0.0-dev"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "AGPL-3.0-or-later"
+ ],
+ "authors": [
+ {
+ "name": "Christoph Wurst",
+ "email": "christoph@winzerhof-wurst.at"
+ },
+ {
+ "name": "Joas Schilling",
+ "email": "coding@schilljs.com"
+ }
+ ],
+ "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/v31.0.9"
+ },
+ "time": "2025-07-31T00:57:37+00:00"
+ },
+ {
+ "name": "php-cs-fixer/shim",
+ "version": "v3.93.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHP-CS-Fixer/shim.git",
+ "reference": "3a9db22e8f01762fddd3a85b998053294c5a3629"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/3a9db22e8f01762fddd3a85b998053294c5a3629",
+ "reference": "3a9db22e8f01762fddd3a85b998053294c5a3629",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "ext-tokenizer": "*",
+ "php": "^7.4 || ^8.0"
+ },
+ "replace": {
+ "friendsofphp/php-cs-fixer": "self.version"
+ },
+ "suggest": {
+ "ext-dom": "For handling output formats in XML",
+ "ext-mbstring": "For handling non-UTF8 characters."
+ },
+ "bin": [
+ "php-cs-fixer",
+ "php-cs-fixer.phar"
+ ],
+ "type": "application",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Dariusz RumiΕski",
+ "email": "dariusz.ruminski@gmail.com"
+ }
+ ],
+ "description": "A tool to automatically fix PHP code style",
+ "support": {
+ "issues": "https://github.com/PHP-CS-Fixer/shim/issues",
+ "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.93.1"
+ },
+ "time": "2026-01-28T23:51:14+00:00"
+ },
+ {
+ "name": "psr/clock",
+ "version": "1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/clock.git",
+ "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d",
+ "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0 || ^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Psr\\Clock\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for reading the clock.",
+ "homepage": "https://github.com/php-fig/clock",
+ "keywords": [
+ "clock",
+ "now",
+ "psr",
+ "psr-20",
+ "time"
+ ],
+ "support": {
+ "issues": "https://github.com/php-fig/clock/issues",
+ "source": "https://github.com/php-fig/clock/tree/1.0.0"
+ },
+ "time": "2022-11-25T14:36:26+00:00"
+ },
+ {
+ "name": "psr/container",
+ "version": "2.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/container.git",
+ "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+ "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.4.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Container\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common Container Interface (PHP FIG PSR-11)",
+ "homepage": "https://github.com/php-fig/container",
+ "keywords": [
+ "PSR-11",
+ "container",
+ "container-interface",
+ "container-interop",
+ "psr"
+ ],
+ "support": {
+ "issues": "https://github.com/php-fig/container/issues",
+ "source": "https://github.com/php-fig/container/tree/2.0.2"
+ },
+ "time": "2021-11-05T16:47:00+00:00"
+ },
+ {
+ "name": "psr/event-dispatcher",
+ "version": "1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/event-dispatcher.git",
+ "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0",
+ "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\EventDispatcher\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Standard interfaces for event handling.",
+ "keywords": [
+ "events",
+ "psr",
+ "psr-14"
+ ],
+ "support": {
+ "issues": "https://github.com/php-fig/event-dispatcher/issues",
+ "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0"
+ },
+ "time": "2019-01-08T18:20:26+00:00"
+ },
+ {
+ "name": "psr/log",
+ "version": "3.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/log.git",
+ "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
+ "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.0.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Log\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for logging libraries",
+ "homepage": "https://github.com/php-fig/log",
+ "keywords": [
+ "log",
+ "psr",
+ "psr-3"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/log/tree/3.0.2"
+ },
+ "time": "2024-09-11T13:17:53+00:00"
+ },
+ {
+ "name": "squizlabs/php_codesniffer",
+ "version": "3.13.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git",
+ "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0ca86845ce43291e8f5692c7356fccf3bcf02bf4",
+ "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4",
+ "shasum": ""
+ },
+ "require": {
+ "ext-simplexml": "*",
+ "ext-tokenizer": "*",
+ "ext-xmlwriter": "*",
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4"
+ },
+ "bin": [
+ "bin/phpcbf",
+ "bin/phpcs"
+ ],
+ "type": "library",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Greg Sherwood",
+ "role": "Former lead"
+ },
+ {
+ "name": "Juliette Reinders Folmer",
+ "role": "Current lead"
+ },
+ {
+ "name": "Contributors",
+ "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors"
+ }
+ ],
+ "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
+ "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer",
+ "keywords": [
+ "phpcs",
+ "standards",
+ "static analysis"
+ ],
+ "support": {
+ "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues",
+ "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy",
+ "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer",
+ "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/PHPCSStandards",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/jrfnl",
+ "type": "github"
+ },
+ {
+ "url": "https://opencollective.com/php_codesniffer",
+ "type": "open_collective"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/phpcsstandards",
+ "type": "thanks_dev"
+ }
+ ],
+ "time": "2025-11-04T16:30:35+00:00"
+ }
+ ],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": [],
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": {
+ "php": "^8.1"
+ },
+ "platform-dev": [],
+ "platform-overrides": {
+ "php": "8.1"
+ },
+ "plugin-api-version": "2.6.0"
+}
diff --git a/css/admin.css b/css/admin.css
index b953487..2f8ef22 100644
--- a/css/admin.css
+++ b/css/admin.css
@@ -63,6 +63,23 @@
font-size: 0.9em;
}
+/* Options section */
+.nldesign-option {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 16px;
+ border: 1px solid var(--color-border);
+ border-radius: var(--border-radius-large);
+ background: var(--color-main-background);
+ margin-bottom: 1em;
+}
+
+.nldesign-option label {
+ cursor: pointer;
+ flex: 1;
+}
+
/* Preview section */
.nldesign-preview {
margin-top: 2em;
diff --git a/css/fonts.css b/css/fonts.css
index e074ef7..c0b64fa 100644
--- a/css/fonts.css
+++ b/css/fonts.css
@@ -5,18 +5,11 @@
* from @rijkshuisstijl-community/font package
*/
-/* Import Fira Sans from fontsource */
-@import url('~@fontsource/fira-sans/400.css');
-@import url('~@fontsource/fira-sans/400-italic.css');
-@import url('~@fontsource/fira-sans/700.css');
-@import url('~@fontsource/fira-sans/700-italic.css');
-
-/* Fallback for direct file access */
@font-face {
font-family: 'Fira Sans';
src: local('Fira Sans'),
- url('../fonts/fira-sans-400-normal.woff2') format('woff2'),
- url('../fonts/fira-sans-400-normal.woff') format('woff');
+ url('fonts/fira-sans-latin-400-normal.woff2') format('woff2'),
+ url('fonts/fira-sans-latin-400-normal.woff') format('woff');
font-weight: 400;
font-style: normal;
font-display: swap;
@@ -25,8 +18,8 @@
@font-face {
font-family: 'Fira Sans';
src: local('Fira Sans Italic'),
- url('../fonts/fira-sans-400-italic.woff2') format('woff2'),
- url('../fonts/fira-sans-400-italic.woff') format('woff');
+ url('fonts/fira-sans-latin-400-italic.woff2') format('woff2'),
+ url('fonts/fira-sans-latin-400-italic.woff') format('woff');
font-weight: 400;
font-style: italic;
font-display: swap;
@@ -35,8 +28,8 @@
@font-face {
font-family: 'Fira Sans';
src: local('Fira Sans Bold'),
- url('../fonts/fira-sans-700-normal.woff2') format('woff2'),
- url('../fonts/fira-sans-700-normal.woff') format('woff');
+ url('fonts/fira-sans-latin-700-normal.woff2') format('woff2'),
+ url('fonts/fira-sans-latin-700-normal.woff') format('woff');
font-weight: 700;
font-style: normal;
font-display: swap;
@@ -45,8 +38,8 @@
@font-face {
font-family: 'Fira Sans';
src: local('Fira Sans Bold Italic'),
- url('../fonts/fira-sans-700-italic.woff2') format('woff2'),
- url('../fonts/fira-sans-700-italic.woff') format('woff');
+ url('fonts/fira-sans-latin-700-italic.woff2') format('woff2'),
+ url('fonts/fira-sans-latin-700-italic.woff') format('woff');
font-weight: 700;
font-style: italic;
font-display: swap;
diff --git a/css/hide-slogan.css b/css/hide-slogan.css
new file mode 100644
index 0000000..cea4e16
--- /dev/null
+++ b/css/hide-slogan.css
@@ -0,0 +1,16 @@
+/**
+ * Hide Nextcloud slogan/payoff on login page
+ *
+ * This CSS file is loaded when the admin has enabled the "Hide slogan" setting
+ * in the NL Design System Theme admin panel.
+ */
+
+/* Hide the footer.guest-box that contains the payoff/slogan */
+footer.guest-box,
+#body-login footer.guest-box,
+body.body-login-container footer.guest-box {
+ display: none !important;
+ visibility: hidden !important;
+}
+
+
diff --git a/css/logo-amsterdam.css b/css/logo-amsterdam.css
deleted file mode 100644
index f60685f..0000000
--- a/css/logo-amsterdam.css
+++ /dev/null
@@ -1,18 +0,0 @@
-/**
- * NL Design System - Amsterdam Logo
- *
- * Coat of arms for the municipality of Amsterdam
- */
-
-/* Amsterdam coat of arms */
-#nextcloud::before {
- content: '' !important;
- display: block !important;
- width: 32px !important;
- height: 40px !important;
- background-image: url('../img/amsterdam-logo.svg') !important;
- background-size: contain !important;
- background-repeat: no-repeat !important;
- background-position: center !important;
- text-indent: 0 !important;
-}
diff --git a/css/logo-denhaag.css b/css/logo-denhaag.css
deleted file mode 100644
index 50bcf3f..0000000
--- a/css/logo-denhaag.css
+++ /dev/null
@@ -1,18 +0,0 @@
-/**
- * NL Design System - Den Haag Logo
- *
- * Coat of arms for the municipality of Den Haag (The Hague)
- */
-
-/* Den Haag coat of arms */
-#nextcloud::before {
- content: '' !important;
- display: block !important;
- width: 34px !important;
- height: 40px !important;
- background-image: url('../img/denhaag-logo.svg') !important;
- background-size: contain !important;
- background-repeat: no-repeat !important;
- background-position: center !important;
- text-indent: 0 !important;
-}
diff --git a/css/logo-rijkshuisstijl.css b/css/logo-rijkshuisstijl.css
deleted file mode 100644
index b94ed4d..0000000
--- a/css/logo-rijkshuisstijl.css
+++ /dev/null
@@ -1,18 +0,0 @@
-/**
- * NL Design System - Rijkshuisstijl Logo
- *
- * Logo for the national government (Nederland map)
- */
-
-/* Nederland map icon (larger, centered, proper aspect ratio) */
-#nextcloud::before {
- content: '' !important;
- display: block !important;
- width: 28px !important;
- height: 36px !important;
- background-image: url('../img/nederland-map.svg') !important;
- background-size: contain !important;
- background-repeat: no-repeat !important;
- background-position: center !important;
- text-indent: 0 !important;
-}
diff --git a/css/logo-rotterdam.css b/css/logo-rotterdam.css
deleted file mode 100644
index f1c1863..0000000
--- a/css/logo-rotterdam.css
+++ /dev/null
@@ -1,18 +0,0 @@
-/**
- * NL Design System - Rotterdam Logo
- *
- * Coat of arms for the municipality of Rotterdam
- */
-
-/* Rotterdam coat of arms */
-#nextcloud::before {
- content: '' !important;
- display: block !important;
- width: 32px !important;
- height: 36px !important;
- background-image: url('../img/rotterdam-logo.svg') !important;
- background-size: contain !important;
- background-repeat: no-repeat !important;
- background-position: center !important;
- text-indent: 0 !important;
-}
diff --git a/css/logo-utrecht.css b/css/logo-utrecht.css
deleted file mode 100644
index 0bbbf8a..0000000
--- a/css/logo-utrecht.css
+++ /dev/null
@@ -1,18 +0,0 @@
-/**
- * NL Design System - Utrecht Logo
- *
- * Coat of arms for the municipality of Utrecht
- */
-
-/* Utrecht coat of arms */
-#nextcloud::before {
- content: '' !important;
- display: block !important;
- width: 36px !important;
- height: 36px !important;
- background-image: url('../img/utrecht-logo.svg') !important;
- background-size: contain !important;
- background-repeat: no-repeat !important;
- background-position: center !important;
- text-indent: 0 !important;
-}
diff --git a/css/nuclear.css b/css/nuclear.css
deleted file mode 100644
index e34bb3f..0000000
--- a/css/nuclear.css
+++ /dev/null
@@ -1,17 +0,0 @@
-/**
- * GRADIENT HANDLING - DOCUMENTED LIMITATION
- *
- * Nextcloud core uses inline CSS gradients (background-image: linear-gradient())
- * combined with -webkit-mask-image for most navigation icons.
- *
- * These inline styles have the highest CSS specificity and cannot be overridden
- * with external stylesheets. This is a Nextcloud core limitation.
- *
- * DECISION: Accept gradient icons as-is to maintain stability and functionality.
- *
- * Note: SVG-based icons (like some app icons) can have gradients removed,
- * but this affects a minority of icons and isn't worth the trade-offs.
- */
-
-/* This file intentionally left minimal to document the limitation. */
-/* No aggressive overrides that break layout or functionality. */
diff --git a/css/overrides.css b/css/overrides.css
index 8abbdd7..88bdf71 100644
--- a/css/overrides.css
+++ b/css/overrides.css
@@ -34,6 +34,175 @@ ol {
font-family: 'Fira Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Cantarell, Ubuntu, 'Helvetica Neue', Arial, sans-serif !important;
}
+/* ============================================
+ OVERRIDE NEXTCLOUD CONTAINER VARIABLES
+ ============================================ */
+
+/* Override body container radius to use NL Design border radius. */
+:root,
+body {
+ --body-container-radius: var(--nldesign-border-radius) !important;
+ --body-container-margin: 15px !important;
+}
+
+/* NL Design Layout - Separate navigation and content cards. */
+/* Main content wrapper should be transparent to show background. */
+#content,
+.content[data-v-1f87d811] {
+ background: transparent !important;
+ background-color: transparent !important;
+}
+
+/* App navigation (left sidebar) as white card with margin. */
+#app-navigation,
+.app-navigation,
+#app-navigation-vue {
+ background: var(--color-main-background) !important;
+ background-color: var(--color-main-background) !important;
+ margin-right: 30px !important;
+ border-radius: var(--nldesign-border-radius) !important;
+}
+
+/* Remove margin when navigation is closed. */
+#app-navigation.app-navigation--close,
+.app-navigation.app-navigation--close {
+ margin-right: 0 !important;
+}
+
+/* Navigation toggle button - remove excessive padding. */
+.app-navigation-toggle,
+.app-navigation-toggle.button-vue,
+button.app-navigation-toggle {
+ padding: 0 !important;
+ padding-inline: 0 !important;
+ padding-block: 0 !important;
+ min-width: var(--default-clickable-area) !important;
+ min-height: var(--default-clickable-area) !important;
+}
+
+/* App content (main area) as white card. */
+#app-content,
+.app-content,
+#app-content-vue,
+.app-content-wrapper {
+ background: var(--color-main-background) !important;
+ background-color: var(--color-main-background) !important;
+ border-radius: var(--nldesign-border-radius) !important;
+}
+
+/* MyDash specific styling - transparent background and no padding/margins. */
+/* Target the NcAppContent component wrapper for MyDash. */
+body #content #app-content-vue.app-content,
+body #content main.app-content,
+body #app-mydash .app-content,
+body #app-mydash #app-content-vue,
+body #app-mydash main.app-content,
+body [data-v-e64fb40a][data-v-1f87d811].app-content,
+body main[data-v-e64fb40a].app-content,
+#mydash-app {
+ background: transparent !important;
+ background-color: transparent !important;
+ margin: 0 !important;
+ padding: 0 !important;
+}
+
+/* MyDash content wrapper - special margins. */
+#content.app-mydash {
+ margin-left: 2px !important;
+ margin-right: 0 !important;
+ margin-bottom: 0 !important;
+ margin-top: 40px !important;
+}
+
+/* Even more specific - target by parent app structure. */
+#content-vue[data-app="mydash"] .app-content,
+div[id*="mydash"] ~ .app-content,
+body:has(#mydash-app) #app-content-vue,
+body:has(#mydash-app) main.app-content {
+ background: transparent !important;
+ background-color: transparent !important;
+}
+
+.mydash-container {
+ padding: 0 !important;
+ margin: 0 !important;
+}
+
+/* ============================================
+ FIX HEADER-END SPACING
+ ============================================ */
+
+/* Add padding to the right side of the header end section and remove margin. */
+.header-end,
+#header .header-end,
+header#header .header-end {
+ padding-right: 15px !important;
+ margin: 0 !important;
+}
+
+/* Add right positioning to header menu trigger. */
+.header-menu__trigger,
+#header .header-menu__trigger,
+header#header .header-menu__trigger {
+ right: 12px !important;
+}
+
+/* ============================================
+ FIX HEADER-END ICONS (make them visible on white background)
+ ============================================ */
+
+/* Invert icons in header-end section to make them dark on white background. */
+html body #header .header-end svg,
+body #header .header-end svg,
+#header .header-end svg,
+html body #header .header-end .button-vue__icon,
+body #header .header-end .button-vue__icon,
+#header .header-end .button-vue__icon,
+html body #header .header-end .icon-vue,
+body #header .header-end .icon-vue,
+#header .header-end .icon-vue,
+html body #header .header-end img,
+body #header .header-end img,
+#header .header-end img {
+ filter: invert(1) brightness(0) contrast(100) !important;
+ -webkit-filter: invert(1) brightness(0) contrast(100) !important;
+ opacity: 1 !important;
+}
+
+/* Exception: Don't invert the avatar image. */
+html body #header .header-end .avatardiv img,
+body #header .header-end .avatardiv img,
+#header .header-end .avatardiv img {
+ filter: none !important;
+ -webkit-filter: none !important;
+}
+
+/* Exception: Don't invert the user status icon (it has its own color). */
+html body #header .header-end .user-status-icon svg,
+body #header .header-end .user-status-icon svg,
+#header .header-end .user-status-icon svg {
+ filter: none !important;
+ -webkit-filter: none !important;
+}
+
+/* Remove gradient masks from header-end icons. */
+html body #header .header-end .icon-vue,
+body #header .header-end .icon-vue,
+#header .header-end .icon-vue,
+html body #header .header-end .button-vue__icon,
+body #header .header-end .button-vue__icon,
+#header .header-end .button-vue__icon,
+html body #header .header-end [class*="icon"],
+body #header .header-end [class*="icon"],
+#header .header-end [class*="icon"] {
+ mask: none !important;
+ -webkit-mask: none !important;
+ mask-image: none !important;
+ -webkit-mask-image: none !important;
+ background-image: none !important;
+ background: transparent !important;
+}
+
/* ============================================
FIX "GOEDEMORGEN" VISIBILITY
============================================ */
@@ -99,9 +268,11 @@ header nav .icon {
NEXTCLOUD LOGO REPLACEMENT
============================================ */
-/* Hide original logo image */
-#nextcloud .logo,
-#nextcloud .logo-icon,
+/* Note: Logo is now handled via normal Nextcloud theming.
+ Apps like mydash can style the logo container as needed.
+ We keep #nextcloud minimal and let the .logo.logo-icon show normally. */
+
+/* Hide direct svg/img children (not the .logo-icon div) */
#nextcloud > svg,
#nextcloud > img,
#nextcloud > a > svg,
@@ -110,25 +281,18 @@ header nav .icon {
visibility: hidden !important;
}
-/* Hide any text content inside */
-#nextcloud,
-#nextcloud a,
-#nextcloud span,
-#nextcloud div {
- font-size: 0 !important;
- color: transparent !important;
- text-indent: -9999px !important;
+/* Let .logo and .logo-icon be visible - they contain the themed logo */
+#nextcloud .logo,
+#nextcloud .logo-icon {
+ /* Don't hide these - they contain the configured logo via background-image. */
}
-/* Show only logo icon (specific icon loaded via separate CSS based on token set) */
+/* Basic nextcloud link styling */
#nextcloud {
visibility: visible !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
- width: 50px !important;
- height: 50px !important;
- padding: 8px !important;
}
/* Logo icon styling is defined in logo-{tokenset}.css files */
@@ -138,12 +302,12 @@ header nav .icon {
============================================ */
/* Set default solid backgrounds, but allow custom widget styles to override */
-/* Exclude tiles and MyDash widgets to allow custom styling */
-.panel:not(.tile-widget):not(.tile-widget *):not(.mydash-widget):not(.mydash-widget *),
-.widget:not(.tile-widget):not(.tile-widget *):not(.mydash-widget):not(.mydash-widget *),
-.dashboard-widget:not(.tile-widget):not(.tile-widget *):not(.mydash-widget):not(.mydash-widget *),
-[class*="panel"]:not(.tile-widget):not(.tile-widget *):not(.mydash-widget):not(.mydash-widget *),
-[class*="widget"]:not(.tile-widget):not(.tile-widget *):not(.mydash-widget):not(.mydash-widget *),
+/* Exclude tiles, MyDash widgets, and header elements to allow custom styling */
+.panel:not(.tile-widget):not(.tile-widget *):not(.mydash-widget):not(.mydash-widget *):not(#header *),
+.widget:not(.tile-widget):not(.tile-widget *):not(.mydash-widget):not(.mydash-widget *):not(#header *),
+.dashboard-widget:not(.tile-widget):not(.tile-widget *):not(.mydash-widget):not(.mydash-widget *):not(#header *),
+[class*="panel"]:not(.tile-widget):not(.tile-widget *):not(.mydash-widget):not(.mydash-widget *):not(#header *),
+[class*="widget"]:not(.tile-widget):not(.tile-widget *):not(.mydash-widget):not(.mydash-widget *):not(#header *),
#app-content,
.app-content {
background: #ffffff;
@@ -309,3 +473,479 @@ select,
#expand:hover {
border-radius: var(--nldesign-border-radius) !important;
}
+
+/* ============================================
+ LOGIN PAGE - RIJKSOVERHEID BLUE BAR
+ ============================================
+
+ Blue bar with logo at top center:
+ - Blue bar: 60px wide Γ 90px high
+ - Logo: 50px wide, positioned at bottom with 5px margin
+ ============================================ */
+
+/* Narrow blue bar at top center. */
+#body-login .guest-box.login-box::before,
+#body-login div.guest-box.login-box::before,
+div.guest-box.login-box::before {
+ content: '' !important;
+ position: absolute !important;
+ top: 0 !important;
+ left: 50% !important;
+ transform: translateX(-50%) !important;
+ width: 60px !important;
+ height: 90px !important;
+ background: #154273 !important;
+ z-index: 1000 !important;
+ display: block !important;
+ pointer-events: none !important;
+}
+
+/* White logo in the narrow blue bar - positioned at bottom. */
+#body-login .guest-box.login-box::after,
+#body-login div.guest-box.login-box::after,
+div.guest-box.login-box::after {
+ content: '' !important;
+ position: absolute !important;
+ top: 35px !important; /* 90px - 50px logo - 5px margin = 35px from top */
+ left: 50% !important;
+ transform: translateX(-50%) !important;
+ width: 50px !important;
+ height: 50px !important;
+ background-image: var(--image-logo, url('../img/nederland-logo.svg')) !important;
+ background-repeat: no-repeat !important;
+ background-position: center !important;
+ background-size: contain !important;
+ filter: brightness(0) invert(1) !important;
+ z-index: 1001 !important;
+ display: block !important;
+ pointer-events: none !important;
+}
+
+/* Push guest-box content down. */
+#body-login .guest-box.login-box,
+#body-login div.guest-box.login-box,
+div.guest-box.login-box {
+ position: relative !important;
+ padding-top: 90px !important;
+ overflow: visible !important;
+}
+
+/* ============================================
+ LOGGED-IN HEADER STYLING (NL DESIGN)
+ ============================================
+
+ This section applies NL Design styling to the Nextcloud header:
+ - White header background
+ - Blue bar with logo at left (using configured Nextcloud logo)
+ - Dark text and icons on white background
+ - Token-based colors for consistency
+ ============================================ */
+
+/* Remove any top margin/padding from body to eliminate white space. */
+html,
+body {
+ margin-top: 0 !important;
+ padding-top: 0 !important;
+}
+
+/* Allow header to overflow for blue bar sticking out. */
+html body.body-user #body-user,
+body.body-user #body-user,
+html body #body-user,
+body #body-user,
+#body-user {
+ overflow: visible !important;
+ margin-top: 0 !important;
+ padding-top: 0 !important;
+}
+
+/* White header background. */
+html body.body-user #body-user #header,
+body.body-user #body-user #header,
+html body #body-user #header,
+body #body-user #header,
+html body #header,
+body #header,
+#body-user #header,
+#header {
+ background-color: #ffffff !important;
+ background-image: none !important;
+ background: #ffffff !important;
+ border-bottom: var(--nldesign-header-border-bottom, 0) !important;
+ position: relative !important;
+ overflow: visible !important;
+}
+
+/* Blue bar container - target the actual nextcloud logo link. */
+html body #header a#nextcloud,
+body #header a#nextcloud,
+#header a#nextcloud,
+html body #header a[href="/"],
+body #header a[href="/"],
+#header a[href="/"] {
+ position: fixed !important;
+ left: 15px !important;
+ top: 0 !important;
+ width: 60px !important;
+ height: 90px !important;
+ margin: 0 !important;
+ background: #154273 !important;
+ background-color: #154273 !important;
+ display: flex !important;
+ flex-direction: column !important;
+ align-items: center !important;
+ justify-content: flex-end !important;
+ padding: 0 !important;
+ overflow: hidden !important;
+ box-sizing: border-box !important;
+ text-align: center !important;
+}
+
+/* Disable pseudo-element logo approach. */
+html body #header a#nextcloud::before,
+body #header a#nextcloud::before,
+#header a#nextcloud::before,
+html body #header a[href="/"]::before,
+body #header a[href="/"]::before,
+#header a[href="/"]::before {
+ content: none !important;
+ display: none !important;
+}
+
+/* Logo element inside the blue bar - use the actual Nextcloud configured logo. */
+html body #header a#nextcloud .logo.logo-icon,
+body #header a#nextcloud .logo.logo-icon,
+#header a#nextcloud .logo.logo-icon,
+html body #header a[href="/"] .logo.logo-icon,
+body #header a[href="/"] .logo.logo-icon,
+#header a[href="/"] .logo.logo-icon {
+ display: block !important;
+ visibility: visible !important;
+ width: 50px !important;
+ min-width: 50px !important;
+ max-width: 50px !important;
+ height: 0 !important;
+ min-height: 50px !important;
+ max-height: 65px !important;
+ background-size: contain !important;
+ background-repeat: no-repeat !important;
+ background-position: center center !important;
+ background-color: transparent !important;
+ background-image: var(--image-logoheader, var(--image-logo, url(../img/logo/logo.svg))) !important;
+ filter: brightness(0) invert(1) !important;
+ opacity: 1 !important;
+ margin: 0 5px 5px 5px !important;
+ padding: 0 !important;
+ flex-shrink: 0 !important;
+ flex-grow: 0 !important;
+ position: static !important;
+ text-indent: 0 !important;
+ overflow: visible !important;
+ border: none !important;
+ box-shadow: none !important;
+ font-size: 0 !important;
+}
+
+/* Remove old pseudo-elements. */
+html body #header::before,
+body #header::before,
+#header::before,
+html body #header::after,
+body #header::after,
+#header::after {
+ content: none !important;
+ display: none !important;
+}
+
+/* Push header left content to make space for blue bar. */
+html body #header .header-left,
+body #header .header-left,
+#header .header-left,
+html body #header .header-start,
+body #header .header-start,
+#header .header-start {
+ position: relative !important;
+ z-index: 1000 !important;
+ padding-left: 80px !important;
+}
+
+/* App menu - remove padding since logo is in normal flow. */
+html body #header nav.app-menu,
+body #header nav.app-menu,
+#header nav.app-menu {
+ background: transparent !important;
+ padding-left: 0 !important;
+ margin-left: 0 !important;
+}
+
+html body #header nav.app-menu ul,
+body #header nav.app-menu ul,
+#header nav.app-menu ul {
+ padding-left: 0 !important;
+ margin-left: 0 !important;
+}
+
+html body #header nav.app-menu li,
+body #header nav.app-menu li,
+#header nav.app-menu li {
+ margin-left: 0 !important;
+}
+
+/* App menu styling - use theme colors. */
+html body #header nav.app-menu a,
+body #header nav.app-menu a,
+#header nav.app-menu a,
+html body #header nav.app-menu .app-menu-entry__link,
+body #header nav.app-menu .app-menu-entry__link,
+#header nav.app-menu .app-menu-entry__link {
+ color: var(--color-main-text) !important;
+ font-weight: 400 !important;
+}
+
+/* App menu text labels - explicitly set dark color. */
+html body #header nav.app-menu .app-menu-entry__label,
+body #header nav.app-menu .app-menu-entry__label,
+#header nav.app-menu .app-menu-entry__label,
+html body #header nav.app-menu .app-menu-entry .app-menu-entry__label,
+body #header nav.app-menu .app-menu-entry .app-menu-entry__label,
+#header nav.app-menu .app-menu-entry .app-menu-entry__label {
+ color: var(--color-main-text) !important;
+ font-weight: 400 !important;
+}
+
+/* App menu icons - invert if SVGs are white to make them dark. */
+/* CRITICAL: If SVGs are white, invert them to dark for white header background. */
+/* Maximum specificity to override #header .app-menu-icon from theme.css */
+html body #header nav.app-menu .app-menu-entry .app-menu-icon img.app-menu-icon__icon,
+html body #header nav.app-menu .app-menu-entry .app-menu-icon img,
+html body #header nav.app-menu .app-menu-icon img,
+body #header nav.app-menu .app-menu-icon img,
+#header nav.app-menu .app-menu-icon img,
+html body #header nav.app-menu .app-menu-icon__icon,
+body #header nav.app-menu .app-menu-icon__icon,
+#header nav.app-menu .app-menu-icon__icon,
+html body #header nav.app-menu .app-menu-entry .app-menu-icon,
+html body #header nav.app-menu .app-menu-icon,
+body #header nav.app-menu .app-menu-icon,
+#header nav.app-menu .app-menu-icon,
+html body #header nav.app-menu .app-menu-entry__icon,
+body #header nav.app-menu .app-menu-entry__icon,
+#header nav.app-menu .app-menu-entry__icon,
+html body #header nav.app-menu .app-menu-entry__icon img,
+body #header nav.app-menu .app-menu-entry__icon img,
+#header nav.app-menu .app-menu-entry__icon img,
+html body #header nav.app-menu img,
+body #header nav.app-menu img,
+#header nav.app-menu img {
+ filter: invert(1) brightness(0) contrast(100) !important;
+ -webkit-filter: invert(1) brightness(0) contrast(100) !important;
+ opacity: 1 !important;
+}
+
+/* App menu SVGs should also be inverted. */
+html body #header nav.app-menu svg,
+body #header nav.app-menu svg,
+#header nav.app-menu svg,
+html body #header nav.app-menu .app-menu-icon svg,
+body #header nav.app-menu .app-menu-icon svg,
+#header nav.app-menu .app-menu-icon svg {
+ filter: invert(1) !important;
+ -webkit-filter: invert(1) !important;
+}
+
+html body #header nav.app-menu svg,
+body #header nav.app-menu svg,
+#header nav.app-menu svg,
+html body #header nav.app-menu .app-menu-icon svg,
+body #header nav.app-menu .app-menu-icon svg,
+#header nav.app-menu .app-menu-icon svg {
+ filter: none !important;
+ -webkit-filter: none !important;
+}
+
+/* Active and hover app menu items. */
+html body #header nav.app-menu .app-menu-entry--active a,
+body #header nav.app-menu .app-menu-entry--active a,
+#header nav.app-menu .app-menu-entry--active a,
+html body #header nav.app-menu .app-menu-entry--active .app-menu-entry__link,
+body #header nav.app-menu .app-menu-entry--active .app-menu-entry__link,
+#header nav.app-menu .app-menu-entry--active .app-menu-entry__link {
+ color: var(--color-primary) !important;
+ font-weight: 600 !important;
+ position: relative !important;
+}
+
+/* Blue indicator bar at bottom of active menu item using box-shadow. */
+html body #header nav.app-menu .app-menu-entry--active a::after,
+body #header nav.app-menu .app-menu-entry--active a::after,
+#header nav.app-menu .app-menu-entry--active a::after,
+html body #header nav.app-menu .app-menu-entry--active .app-menu-entry__link::after,
+body #header nav.app-menu .app-menu-entry--active .app-menu-entry__link::after,
+#header nav.app-menu .app-menu-entry--active .app-menu-entry__link::after {
+ content: '' !important;
+ position: absolute !important;
+ bottom: 0 !important;
+ left: 0 !important;
+ right: 0 !important;
+ height: 5px !important;
+ background: var(--color-primary) !important;
+ display: block !important;
+}
+
+/* Ensure all menu entries have consistent bottom spacing. */
+html body #header nav.app-menu .app-menu-entry,
+body #header nav.app-menu .app-menu-entry,
+#header nav.app-menu .app-menu-entry,
+html body #header nav.app-menu .app-menu-entry a,
+body #header nav.app-menu .app-menu-entry a,
+#header nav.app-menu .app-menu-entry a,
+html body #header nav.app-menu .app-menu-entry .app-menu-entry__link,
+body #header nav.app-menu .app-menu-entry .app-menu-entry__link,
+#header nav.app-menu .app-menu-entry .app-menu-entry__link {
+ margin: 0 !important;
+ padding-top: 0 !important;
+ padding-bottom: 0 !important;
+}
+
+/* Active app menu text labels. */
+html body #header nav.app-menu .app-menu-entry--active .app-menu-entry__label,
+body #header nav.app-menu .app-menu-entry--active .app-menu-entry__label,
+#header nav.app-menu .app-menu-entry--active .app-menu-entry__label {
+ color: var(--color-primary) !important;
+ font-weight: 600 !important;
+}
+
+html body #header nav.app-menu a:hover,
+body #header nav.app-menu a:hover,
+#header nav.app-menu a:hover,
+html body #header nav.app-menu .app-menu-entry__link:hover,
+body #header nav.app-menu .app-menu-entry__link:hover,
+#header nav.app-menu .app-menu-entry__link:hover {
+ background-color: var(--color-background-hover) !important;
+ color: var(--color-primary) !important;
+}
+
+/* Hover app menu text labels. */
+html body #header nav.app-menu .app-menu-entry__link:hover .app-menu-entry__label,
+body #header nav.app-menu .app-menu-entry__link:hover .app-menu-entry__label,
+#header nav.app-menu .app-menu-entry__link:hover .app-menu-entry__label {
+ color: var(--color-primary) !important;
+}
+
+/* Header app name and branding text. */
+html body #header .header-appname,
+body #header .header-appname,
+#header .header-appname,
+html body #header .header-left a,
+body #header .header-left a,
+#header .header-left a {
+ color: #154273 !important;
+ font-weight: 600 !important;
+}
+
+/* Header right side icons and buttons. */
+html body #header .header-right a,
+body #header .header-right a,
+#header .header-right a,
+html body #header .header-right button,
+body #header .header-right button,
+#header .header-right button,
+html body #header .menutoggle,
+body #header .menutoggle,
+#header .menutoggle {
+ color: #333333 !important;
+ opacity: 1 !important;
+}
+
+html body #header .header-right [class^="icon-"],
+body #header .header-right [class^="icon-"],
+#header .header-right [class^="icon-"],
+html body #header .header-right svg,
+body #header .header-right svg,
+#header .header-right svg {
+ filter: none !important;
+ opacity: 0.8 !important;
+}
+
+/* Unified search. */
+html body #header .unified-search__button,
+body #header .unified-search__button,
+#header .unified-search__button {
+ color: #333333 !important;
+ filter: none !important;
+}
+
+/* Header dropdown panels and menus - ensure proper colors. */
+html body #header .header-menu,
+body #header .header-menu,
+#header .header-menu,
+html body #header #user-menu,
+body #header #user-menu,
+#header #user-menu,
+html body #header .popover,
+body #header .popover,
+#header .popover {
+ background-color: var(--color-main-background) !important;
+ color: var(--color-main-text) !important;
+}
+
+html body #header .unified-search__button svg,
+body #header .unified-search__button svg,
+#header .unified-search__button svg {
+ fill: #333333 !important;
+ filter: none !important;
+}
+
+/* User menu and notifications. */
+html body #header .header-right .icon-more,
+body #header .header-right .icon-more,
+#header .header-right .icon-more,
+html body #header .header-right .notifications,
+body #header .header-right .notifications,
+#header .header-right .notifications,
+html body #header .header-right .contactsmenu,
+body #header .header-right .contactsmenu,
+#header .header-right .contactsmenu {
+ opacity: 0.8 !important;
+}
+
+html body #header .header-right #settings,
+body #header .header-right #settings,
+#header .header-right #settings {
+ filter: none !important;
+ opacity: 0.8 !important;
+}
+
+/* Legacy #appmenu support. */
+html body #header #appmenu,
+body #header #appmenu,
+#header #appmenu {
+ background: transparent !important;
+ padding-left: 60px !important;
+ margin-left: 0 !important;
+}
+
+html body #header #appmenu li a,
+body #header #appmenu li a,
+#header #appmenu li a {
+ color: var(--color-main-text) !important;
+ font-weight: 400 !important;
+}
+
+html body #header #appmenu li.active a,
+body #header #appmenu li.active a,
+#header #appmenu li.active a,
+html body #header #appmenu li:hover a,
+body #header #appmenu li:hover a,
+#header #appmenu li:hover a {
+ background-color: var(--color-background-hover) !important;
+ color: var(--color-primary) !important;
+ font-weight: 600 !important;
+}
+
+html body #header .header-appname-container,
+body #header .header-appname-container,
+#header .header-appname-container {
+ position: relative !important;
+ z-index: auto !important;
+}
diff --git a/css/show-menu-labels.css b/css/show-menu-labels.css
new file mode 100644
index 0000000..dd455a0
--- /dev/null
+++ b/css/show-menu-labels.css
@@ -0,0 +1,65 @@
+/**
+ * SPDX-FileCopyrightText: 2024 NLDesign Contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ *
+ * Show Menu Labels Mode
+ * Shows app names instead of icons in the header menu
+ */
+
+/* Hide app menu icons. */
+#header nav.app-menu .app-menu-icon,
+#header nav.app-menu .app-menu-entry__icon {
+ display: none !important;
+ visibility: hidden !important;
+}
+
+/* Make labels always visible and properly styled. */
+#header nav.app-menu .app-menu-entry__label {
+ display: inline-block !important;
+ visibility: visible !important;
+ opacity: 1 !important;
+ font-size: 14px !important;
+ font-weight: 400 !important;
+ padding: 0 8px !important;
+ line-height: 1.4 !important;
+ white-space: nowrap !important;
+ text-align: center !important;
+ vertical-align: middle !important;
+ position: static !important;
+ top: auto !important;
+ bottom: auto !important;
+ left: auto !important;
+ right: auto !important;
+ transform: none !important;
+ max-width: none !important;
+}
+
+/* Active label styling. */
+#header nav.app-menu .app-menu-entry--active .app-menu-entry__label {
+ font-weight: 600 !important;
+}
+
+/* Disable Nextcloud's default active indicator (black dot from AppMenuEntry.vue). */
+#header nav.app-menu .app-menu-entry--active::before {
+ background-color: transparent !important;
+ opacity: 0 !important;
+}
+
+/* Menu entry link should be full height and center content. */
+#header nav.app-menu .app-menu-entry__link {
+ height: 100% !important;
+ display: flex !important;
+ flex-direction: column !important;
+ align-items: center !important;
+ justify-content: center !important;
+ padding: 0 !important;
+}
+
+/* Menu entry item full height. */
+#header nav.app-menu .app-menu-entry {
+ height: var(--header-height) !important;
+ min-width: 80px !important;
+ width: auto !important;
+ flex-shrink: 0 !important;
+}
+
diff --git a/css/theme.css b/css/theme.css
index 2b5f463..9738aba 100644
--- a/css/theme.css
+++ b/css/theme.css
@@ -20,8 +20,8 @@ body {
--color-primary-element-light-text: var(--nldesign-color-primary) !important;
--color-primary-element-light-hover: var(--nldesign-color-primary-light-hover) !important;
- /* Background colors */
- --color-main-background: var(--nldesign-color-background) !important;
+ /* Background colors - use Nextcloud's own theming system. */
+ /* --color-main-background: Managed by Nextcloud theming */
--color-background-hover: var(--nldesign-color-background-hover) !important;
--color-background-dark: var(--nldesign-color-background-dark) !important;
--color-background-darker: var(--nldesign-color-background-darker) !important;
@@ -82,17 +82,6 @@ header#header {
height: 0 !important;
}
-/* Add "Rijksoverheid" or organization name instead of logo */
-#nextcloud::after {
- content: "Rijksoverheid" !important;
- color: var(--nldesign-color-header-text) !important;
- font-family: var(--nldesign-font-family) !important;
- font-size: 20px !important;
- font-weight: 700 !important;
- line-height: 50px !important;
- padding-left: 12px !important;
-}
-
/* Header text and icons */
#header *,
#header .header-appname,
@@ -117,14 +106,156 @@ header#header {
LOGIN PAGE
============================================ */
-/* Remove background image - Rijkshuisstijl uses solid colors */
+/* Light gray background like Rijkshuisstijl - use Nextcloud's theming. */
#body-login,
.body-login-container {
background-image: none !important;
- background-color: var(--nldesign-color-background) !important;
}
-/* Login box primary button */
+/* Login wrapper - centered, clean design */
+#body-login .wrapper {
+ max-width: 500px;
+ margin: 0 auto;
+ box-shadow: none !important;
+ border-radius: 0 !important;
+ padding-top: 0 !important; /* Remove top padding so header is at top of box */
+}
+
+/* Main body-login container positioning */
+#body-login {
+ display: flex !important;
+ flex-direction: column !important;
+ align-items: center !important;
+ justify-content: center !important;
+ min-height: 100vh !important;
+}
+
+/* Login form container - white background, no shadows, sharp corners */
+#body-login form,
+#body-login .login-form,
+#body-login #login-form,
+#body-login .body-login-container > div {
+ background: #ffffff !important;
+ box-shadow: none !important;
+ border-radius: 0 !important;
+ border: none !important;
+}
+
+/* Guest box and login box - clean Rijkshuisstijl style */
+#body-login .guest-box,
+#body-login .login-box,
+.guest-box,
+.login-box {
+ background: #ffffff !important;
+ background-color: #ffffff !important;
+ box-shadow: none !important;
+ border-radius: 0 !important;
+ border: none !important;
+ backdrop-filter: none !important;
+ -webkit-backdrop-filter: none !important;
+ padding: 0 !important; /* Remove padding so header can be full width */
+ overflow: hidden !important; /* Contain the header */
+ position: relative !important; /* For pseudo-element positioning */
+}
+
+/* LOGIN PAGE HEADER - Rijksoverheid style with blue bar and logo */
+/* Remove the margin, make blue bar part of the login box */
+#body-login .guest-box.login-box,
+#body-login .login-box,
+#body-login div.guest-box,
+#body-login div[class*="guest-box"],
+#body-login div[class*="login-box"],
+.guest-box.login-box,
+div.guest-box.login-box,
+div[data-v-48234338].guest-box {
+ position: relative !important;
+ overflow: hidden !important; /* Clip the pseudo-element to box width */
+ margin-top: 0 !important; /* No extra margin needed */
+ padding-top: 0 !important;
+}
+
+/* Create blue bar at TOP of login box (not above it) */
+#body-login .guest-box.login-box::before,
+#body-login div.guest-box::before,
+#body-login div[class*="guest-box"]::before,
+.guest-box.login-box::before,
+div.guest-box.login-box::before,
+div[data-v-48234338].guest-box::before {
+ content: '' !important;
+ position: absolute !important;
+ top: 0 !important; /* At the TOP of the login box */
+ left: 0 !important;
+ right: 0 !important;
+ width: 100% !important; /* Full width of login box */
+ height: 100px !important; /* Blue bar height */
+ background: var(--nldesign-color-button-primary-background) !important;
+ background-color: var(--nldesign-color-button-primary-background) !important;
+ z-index: 1 !important;
+ display: block !important;
+ box-sizing: border-box !important;
+}
+
+/* White logo in the blue bar */
+#body-login .guest-box.login-box::after,
+#body-login div.guest-box::after,
+.guest-box.login-box::after,
+div.guest-box.login-box::after {
+ content: '' !important;
+ position: absolute !important;
+ top: 30px !important; /* Vertically centered in blue bar */
+ left: 50% !important;
+ transform: translateX(-50%) !important;
+ width: 100px !important;
+ height: 40px !important;
+ background-image: url('../img/nederland-logo.svg') !important;
+ background-repeat: no-repeat !important;
+ background-position: center !important;
+ background-size: contain !important;
+ filter: brightness(0) invert(1) !important;
+ z-index: 2 !important;
+ display: block !important;
+}
+
+/* Push login content below blue bar */
+#body-login div[class*="login-box__wrapper"],
+#body-login .guest-box div[class*="login-box__wrapper"],
+div[class*="login-box__wrapper"],
+div[data-v-48234338][class*="login-box__wrapper"],
+.login-box__wrapper[data-v-48234338],
+[data-v-48234338].login-box__wrapper {
+ position: relative !important;
+ background: #ffffff !important;
+ padding-top: 100px !important; /* Space for blue bar */
+ z-index: 3 !important;
+}
+
+/* Form content - normal padding */
+#body-login div[class*="login-box__wrapper"] > form,
+#body-login div[class*="login-box__wrapper"] > div,
+div[class*="login-box__wrapper"] > form,
+div[class*="login-box__wrapper"] > div {
+ padding-top: 12px !important;
+ background: transparent !important;
+}
+
+/* Add padding back to the form content area */
+#body-login .guest-box form,
+#body-login .login-box form,
+#body-login .guest-box > div:not(#header),
+#body-login .login-box > div:not(#header) {
+ padding: 12px !important;
+}
+
+/* Remove all shadows from login elements */
+#body-login input,
+#body-login button,
+#body-login .button-vue,
+#body-login a {
+ box-shadow: none !important;
+ border-radius: 0 !important;
+}
+
+/* Login box primary button - sharp corners */
#body-login .button-vue--vue-primary,
#body-login input[type="submit"],
#body-login button.primary,
@@ -132,19 +263,47 @@ header#header {
background-color: var(--nldesign-color-button-primary-background) !important;
border-color: var(--nldesign-color-button-primary-border) !important;
color: var(--nldesign-color-button-primary-text) !important;
- border-radius: var(--nldesign-border-radius) !important;
+ border-radius: 0 !important;
+ box-shadow: none !important;
}
#body-login .button-vue--vue-primary:hover,
#body-login input[type="submit"]:hover,
#body-login button.primary:hover {
background-color: var(--nldesign-color-button-primary-hover) !important;
+ box-shadow: none !important;
}
-/* Login header/logo area */
+/* Login header/logo area - HIDE the original header that's outside the login box */
#body-login #header,
-body.body-login-container #header {
- background: var(--nldesign-color-header-background) !important;
+#body-login header,
+body.body-login-container #header,
+body.body-login-container header {
+ display: none !important;
+}
+
+/* Login content wrapper */
+#body-login .wrapper,
+#body-login > .wrapper {
+ padding: 0 !important;
+ margin-top: 0 !important;
+}
+
+/* Login form inputs - sharp corners */
+#body-login input[type="text"],
+#body-login input[type="password"],
+#body-login input[type="email"],
+#body-login .input-field {
+ border-radius: 0 !important;
+ box-shadow: none !important;
+ border: 1px solid #d0d0d0 !important;
+}
+
+#body-login input[type="text"]:focus,
+#body-login input[type="password"]:focus,
+#body-login input[type="email"]:focus {
+ border-color: var(--nldesign-color-button-primary-background) !important;
+ box-shadow: none !important;
}
/* ============================================
@@ -282,15 +441,13 @@ body,
DASHBOARD - Remove gradients, backgrounds, transparency
============================================ */
-/* Remove all background images and gradients */
+/* Remove all background images and gradients - use Nextcloud theming for background. */
#body-user,
#content,
.app-dashboard,
#app-content,
#app-content-wrapper {
background-image: none !important;
- background: var(--nldesign-color-background) !important;
- background-color: var(--nldesign-color-background) !important;
}
/* Fix "Goedemorgen" text visibility - ensure it's the right color */
@@ -302,16 +459,15 @@ h2,
background-color: transparent !important;
}
-/* Solid widget backgrounds - no transparency */
-/* Exclude MyDash widgets and tiles to allow custom styling */
+/* Solid widget backgrounds - no transparency. */
+/* Exclude MyDash widgets and tiles to allow custom styling. */
+/* Use Nextcloud theming for background color. */
.panel:not(.mydash-widget):not(.mydash-widget *):not(.tile-widget):not(.tile-widget *),
.panel--wrapper:not(.mydash-widget):not(.mydash-widget *):not(.tile-widget):not(.tile-widget *),
.widget:not(.mydash-widget):not(.mydash-widget *):not(.tile-widget):not(.tile-widget *),
.panel__content:not(.mydash-widget):not(.mydash-widget *):not(.tile-widget):not(.tile-widget *),
.dashboard-widget:not(.mydash-widget):not(.mydash-widget *):not(.tile-widget):not(.tile-widget *),
.dashboard-panel:not(.mydash-widget):not(.mydash-widget *):not(.tile-widget):not(.tile-widget *) {
- background: var(--nldesign-color-background);
- background-color: var(--nldesign-color-background);
opacity: 1;
backdrop-filter: none;
}
@@ -458,13 +614,11 @@ body *,
-webkit-backdrop-filter: none !important;
}
-/* Solid backgrounds everywhere */
+/* Solid backgrounds everywhere - use Nextcloud theming. */
#app-content,
.app-content-wrapper,
.app-content-list,
.app-content-detail {
- background: var(--nldesign-color-background) !important;
- background-color: var(--nldesign-color-background) !important;
background-image: none !important;
}
diff --git a/css/tokens/amsterdam.css b/css/tokens/amsterdam.css
index 4c38709..2545568 100644
--- a/css/tokens/amsterdam.css
+++ b/css/tokens/amsterdam.css
@@ -25,9 +25,8 @@
--amsterdam-color-blue: #004699;
--amsterdam-color-purple: #a00078;
- /* Background colors */
- --nldesign-color-background: #ffffff;
- --nldesign-color-background-rgb: 255, 255, 255;
+ /* Background colors - for hover states and dark variants. */
+ /* Main background managed by Nextcloud theming system. */
--nldesign-color-background-hover: #f5f5f5;
--nldesign-color-background-dark: #e6e6e6;
--nldesign-color-background-darker: #cccccc;
diff --git a/css/tokens/denhaag.css b/css/tokens/denhaag.css
index 6227974..2658909 100644
--- a/css/tokens/denhaag.css
+++ b/css/tokens/denhaag.css
@@ -21,9 +21,8 @@
--denhaag-color-red: #d52d2d;
--denhaag-color-blue: #1261a3;
- /* Background colors */
- --nldesign-color-background: #ffffff;
- --nldesign-color-background-rgb: 255, 255, 255;
+ /* Background colors - for hover states and dark variants. */
+ /* Main background managed by Nextcloud theming system. */
--nldesign-color-background-hover: #f5f5f5;
--nldesign-color-background-dark: #e8e8e8;
--nldesign-color-background-darker: #d4d4d4;
diff --git a/css/tokens/rijkshuisstijl.css b/css/tokens/rijkshuisstijl.css
index e6ac083..eddbc6b 100644
--- a/css/tokens/rijkshuisstijl.css
+++ b/css/tokens/rijkshuisstijl.css
@@ -32,16 +32,16 @@
--rh-color-paars: #42145f;
--rh-color-mauve: #b4a7c9;
- /* Background colors */
- --nldesign-color-background: #ffffff;
- --nldesign-color-background-rgb: 255, 255, 255;
- --nldesign-color-background-hover: #f3f3f3;
- --nldesign-color-background-dark: #e6e6e6;
- --nldesign-color-background-darker: #cccccc;
+ /* Background colors - for hover states and dark variants. */
+ /* Main background managed by Nextcloud theming system. */
+ --nldesign-color-background-hover: #e8e9ea;
+ --nldesign-color-background-dark: #e0e1e2;
+ --nldesign-color-background-darker: #d0d1d2;
/* Header */
--nldesign-color-header-background: #154273;
- --nldesign-color-header-text: #ffffff;
+ --nldesign-color-header-text: #333333;
+ --nldesign-header-border-bottom: 0;
/* Navigation */
--nldesign-color-nav-background: #ffffff;
diff --git a/css/tokens/rotterdam.css b/css/tokens/rotterdam.css
index 0a515eb..d60bea8 100644
--- a/css/tokens/rotterdam.css
+++ b/css/tokens/rotterdam.css
@@ -21,9 +21,8 @@
--rotterdam-color-orange: #ec6d00;
--rotterdam-color-yellow: #ffc800;
- /* Background colors */
- --nldesign-color-background: #ffffff;
- --nldesign-color-background-rgb: 255, 255, 255;
+ /* Background colors - for hover states and dark variants. */
+ /* Main background managed by Nextcloud theming system. */
--nldesign-color-background-hover: #f5f5f5;
--nldesign-color-background-dark: #e8e8e8;
--nldesign-color-background-darker: #d4d4d4;
diff --git a/css/tokens/utrecht.css b/css/tokens/utrecht.css
index 3bc6753..4e9afc0 100644
--- a/css/tokens/utrecht.css
+++ b/css/tokens/utrecht.css
@@ -23,9 +23,8 @@
--utrecht-color-green: #2a5510;
--utrecht-color-blue: #007bc7;
- /* Background colors */
- --nldesign-color-background: #ffffff;
- --nldesign-color-background-rgb: 255, 255, 255;
+ /* Background colors - for hover states and dark variants. */
+ /* Main background managed by Nextcloud theming system. */
--nldesign-color-background-hover: #f5f5f5;
--nldesign-color-background-dark: #e8e8e8;
--nldesign-color-background-darker: #d4d4d4;
diff --git a/img/nederland-logo.svg b/img/nederland-logo.svg
new file mode 100644
index 0000000..8b5e8d7
--- /dev/null
+++ b/img/nederland-logo.svg
@@ -0,0 +1,396 @@
+
+
diff --git a/js/admin.js b/js/admin.js
index 5c84c4c..773b566 100644
--- a/js/admin.js
+++ b/js/admin.js
@@ -4,6 +4,7 @@
document.addEventListener('DOMContentLoaded', function() {
const tokenSetInputs = document.querySelectorAll('input[name="nldesign-token-set"]');
+ const hideSloganCheckbox = document.getElementById('nldesign-hide-slogan');
const previewBox = document.querySelector('.nldesign-preview-box');
// Token set color mappings for preview
@@ -104,4 +105,73 @@ document.addEventListener('DOMContentLoaded', function() {
OC.Notification.showTemporary(t('nldesign', 'Failed to update theme.'));
});
}
+
+ // Handle hide slogan checkbox
+ if (hideSloganCheckbox) {
+ hideSloganCheckbox.addEventListener('change', function() {
+ const hideSlogan = this.checked;
+ saveSloganSetting(hideSlogan);
+ });
+ }
+
+ // Handle show menu labels checkbox
+ const showMenuLabelsCheckbox = document.getElementById('nldesign-show-menu-labels');
+ if (showMenuLabelsCheckbox) {
+ showMenuLabelsCheckbox.addEventListener('change', function() {
+ const showMenuLabels = this.checked;
+ saveMenuLabelsSetting(showMenuLabels);
+ });
+ }
+
+ // Save hide slogan setting to server
+ function saveSloganSetting(hideSlogan) {
+ const url = OC.generateUrl('/apps/nldesign/settings/slogan');
+
+ fetch(url, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'requesttoken': OC.requestToken
+ },
+ body: JSON.stringify({ hideSlogan: hideSlogan })
+ })
+ .then(response => response.json())
+ .then(data => {
+ if (data.status === 'ok') {
+ OC.Notification.showTemporary(t('nldesign', 'Setting saved successfully. Reload the login page to see changes.'));
+ } else {
+ OC.Notification.showTemporary(t('nldesign', 'Failed to save setting.'));
+ }
+ })
+ .catch(error => {
+ console.error('Error saving slogan setting:', error);
+ OC.Notification.showTemporary(t('nldesign', 'Failed to save setting.'));
+ });
+ }
+
+ // Save show menu labels setting to server.
+ function saveMenuLabelsSetting(showMenuLabels) {
+ const url = OC.generateUrl('/apps/nldesign/settings/menulabels');
+
+ fetch(url, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'requesttoken': OC.requestToken
+ },
+ body: JSON.stringify({ showMenuLabels: showMenuLabels })
+ })
+ .then(response => response.json())
+ .then(data => {
+ if (data.status === 'ok') {
+ OC.Notification.showTemporary(t('nldesign', 'Setting saved successfully. Reload the page to see changes.'));
+ } else {
+ OC.Notification.showTemporary(t('nldesign', 'Failed to save setting.'));
+ }
+ })
+ .catch(error => {
+ console.error('Error saving menu labels setting:', error);
+ OC.Notification.showTemporary(t('nldesign', 'Failed to save setting.'));
+ });
+ }
});
diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php
index f9dce4d..787bc9e 100644
--- a/lib/AppInfo/Application.php
+++ b/lib/AppInfo/Application.php
@@ -1,5 +1,15 @@
+ * @license https://www.gnu.org/licenses/agpl-3.0.html AGPL-3.0-or-later
+ * @link https://github.com/ConductionNL/nldesign
+ */
+
declare(strict_types=1);
namespace OCA\NLDesign\AppInfo;
@@ -10,42 +20,85 @@
use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
-class Application extends App implements IBootstrap {
- public const APP_ID = 'nldesign';
-
- public function __construct() {
- parent::__construct(self::APP_ID);
- }
-
- public function register(IRegistrationContext $context): void {
- // Register the theme
- }
-
- public function boot(IBootContext $context): void {
- $serverContainer = $context->getServerContainer();
-
- // Inject our CSS variables
- $this->injectThemeCSS($serverContainer);
- }
-
- private function injectThemeCSS($serverContainer): void {
- $config = $serverContainer->getConfig();
- $tokenSet = $config->getAppValue(self::APP_ID, 'token_set', 'rijkshuisstijl');
-
- // Add fonts (Fira Sans from @fontsource)
- \OCP\Util::addStyle(self::APP_ID, 'fonts');
-
- // Add the CSS file for the selected token set
- \OCP\Util::addStyle(self::APP_ID, 'tokens/' . $tokenSet);
- \OCP\Util::addStyle(self::APP_ID, 'theme');
-
- // Add aggressive overrides last (highest priority)
- \OCP\Util::addStyle(self::APP_ID, 'overrides');
-
- // Add logo for the selected token set
- \OCP\Util::addStyle(self::APP_ID, 'logo-' . $tokenSet);
-
- // Nuclear option for gradients (absolute last)
- \OCP\Util::addStyle(self::APP_ID, 'nuclear');
- }
+/**
+ * Main application class for NL Design.
+ *
+ * Bootstraps the NL Design theme system and injects design tokens.
+ */
+class Application extends App implements IBootstrap
+{
+ public const APP_ID = 'nldesign';
+
+ /**
+ * Constructor.
+ */
+ public function __construct()
+ {
+ parent::__construct(self::APP_ID);
+ }
+
+ /**
+ * Register services and providers.
+ *
+ * @param IRegistrationContext $context The registration context.
+ *
+ * @return void
+ */
+ public function register(IRegistrationContext $context): void
+ {
+ // Register the theme.
+ }
+
+ /**
+ * Boot the application.
+ *
+ * @param IBootContext $context The boot context.
+ *
+ * @return void
+ */
+ public function boot(IBootContext $context): void
+ {
+ $serverContainer = $context->getServerContainer();
+
+ // Inject our CSS variables.
+ $this->injectThemeCSS($serverContainer);
+ }
+
+ /**
+ * Inject theme CSS files based on configuration.
+ *
+ * @param mixed $serverContainer The server container.
+ *
+ * @return void
+ */
+ private function injectThemeCSS($serverContainer): void
+ {
+ $config = $serverContainer->getConfig();
+ $tokenSet = $config->getAppValue(self::APP_ID, 'token_set', 'rijkshuisstijl');
+ $hideSlogan = $config->getAppValue(self::APP_ID, 'hide_slogan', '0') === '1';
+ $showMenuLabels = $config->getAppValue(self::APP_ID, 'show_menu_labels', '0') === '1';
+
+ // Add fonts (Fira Sans from @fontsource).
+ \OCP\Util::addStyle(self::APP_ID, 'fonts');
+
+ // Add the CSS file for the selected token set (organization-specific tokens).
+ \OCP\Util::addStyle(self::APP_ID, 'tokens/' . $tokenSet);
+
+ // Add theme CSS (standard design token application).
+ \OCP\Util::addStyle(self::APP_ID, 'theme');
+
+ // Add aggressive overrides (applies NL Design styling to Nextcloud).
+ // This includes header styling for logged-in pages.
+ \OCP\Util::addStyle(self::APP_ID, 'overrides');
+
+ // Hide slogan if enabled.
+ if ($hideSlogan) {
+ \OCP\Util::addStyle(self::APP_ID, 'hide-slogan');
+ }
+
+ // Show menu labels (instead of icons) if enabled.
+ if ($showMenuLabels) {
+ \OCP\Util::addStyle(self::APP_ID, 'show-menu-labels');
+ }
+ }
}
diff --git a/lib/Controller/SettingsController.php b/lib/Controller/SettingsController.php
index 651b69a..2e3eada 100644
--- a/lib/Controller/SettingsController.php
+++ b/lib/Controller/SettingsController.php
@@ -1,5 +1,15 @@
+ * @license https://www.gnu.org/licenses/agpl-3.0.html AGPL-3.0-or-later
+ * @link https://github.com/ConductionNL/nldesign
+ */
+
declare(strict_types=1);
namespace OCA\NLDesign\Controller;
@@ -10,43 +20,108 @@
use OCP\IConfig;
use OCP\IRequest;
-class SettingsController extends Controller {
- private IConfig $config;
-
- public function __construct(
- string $appName,
- IRequest $request,
- IConfig $config
- ) {
- parent::__construct($appName, $request);
- $this->config = $config;
- }
-
- /**
- * @AuthorizedAdminSetting(settings=OCA\NLDesign\Settings\Admin)
- */
- public function setTokenSet(string $tokenSet): JSONResponse {
- $validSets = ['rijkshuisstijl', 'utrecht', 'amsterdam', 'denhaag', 'rotterdam'];
-
- if (!in_array($tokenSet, $validSets)) {
- return new JSONResponse(['error' => 'Invalid token set'], 400);
- }
-
- $this->config->setAppValue(Application::APP_ID, 'token_set', $tokenSet);
-
- return new JSONResponse(['status' => 'ok', 'tokenSet' => $tokenSet]);
- }
-
- /**
- * @AuthorizedAdminSetting(settings=OCA\NLDesign\Settings\Admin)
- */
- public function getTokenSet(): JSONResponse {
- $tokenSet = $this->config->getAppValue(
- Application::APP_ID,
- 'token_set',
- 'rijkshuisstijl'
- );
-
- return new JSONResponse(['tokenSet' => $tokenSet]);
- }
+/**
+ * Settings controller for NL Design app.
+ *
+ * Handles API requests for managing NL Design theme settings.
+ */
+class SettingsController extends Controller
+{
+ private IConfig $config;
+
+ /**
+ * Constructor.
+ *
+ * @param string $appName The app name.
+ * @param IRequest $request The request object.
+ * @param IConfig $config The config service.
+ */
+ public function __construct(
+ string $appName,
+ IRequest $request,
+ IConfig $config
+ ) {
+ parent::__construct($appName, $request);
+ $this->config = $config;
+ }
+
+ /**
+ * Set the active design token set.
+ *
+ * @param string $tokenSet The token set name.
+ *
+ * @return JSONResponse The response with status and selected token set.
+ *
+ * @AuthorizedAdminSetting(settings=OCA\NLDesign\Settings\Admin)
+ */
+ public function setTokenSet(string $tokenSet): JSONResponse
+ {
+ $validSets = ['rijkshuisstijl', 'utrecht', 'amsterdam', 'denhaag', 'rotterdam'];
+
+ if (!in_array($tokenSet, $validSets)) {
+ return new JSONResponse(['error' => 'Invalid token set'], 400);
+ }
+
+ $this->config->setAppValue(Application::APP_ID, 'token_set', $tokenSet);
+
+ return new JSONResponse(['status' => 'ok', 'tokenSet' => $tokenSet]);
+ }
+
+ /**
+ * Get the currently active design token set.
+ *
+ * @AuthorizedAdminSetting(settings=OCA\NLDesign\Settings\Admin)
+ *
+ * @return JSONResponse The response with the current token set.
+ */
+ public function getTokenSet(): JSONResponse
+ {
+ $tokenSet = $this->config->getAppValue(
+ Application::APP_ID,
+ 'token_set',
+ 'rijkshuisstijl'
+ );
+
+ return new JSONResponse(['tokenSet' => $tokenSet]);
+ }
+
+ /**
+ * Set the hide slogan setting.
+ *
+ * @param bool $hideSlogan Whether to hide the slogan on login page.
+ *
+ * @return JSONResponse The response with the status.
+ *
+ * @AuthorizedAdminSetting(settings=OCA\NLDesign\Settings\Admin)
+ */
+ public function setSloganSetting(bool $hideSlogan): JSONResponse
+ {
+ $this->config->setAppValue(
+ Application::APP_ID,
+ 'hide_slogan',
+ $hideSlogan ? '1' : '0'
+ );
+
+ return new JSONResponse(['status' => 'ok', 'hideSlogan' => $hideSlogan]);
+ }
+
+ /**
+ * Set the show menu labels setting.
+ *
+ * @param bool $showMenuLabels Whether to show text labels in app menu.
+ *
+ * @return JSONResponse The response with the status.
+ *
+ * @AuthorizedAdminSetting(settings=OCA\NLDesign\Settings\Admin)
+ */
+ public function setMenuLabelsSetting(bool $showMenuLabels): JSONResponse
+ {
+ $this->config->setAppValue(
+ Application::APP_ID,
+ 'show_menu_labels',
+ $showMenuLabels ? '1' : '0'
+ );
+
+ return new JSONResponse(['status' => 'ok', 'showMenuLabels' => $showMenuLabels]);
+ }
}
diff --git a/lib/Settings/Admin.php b/lib/Settings/Admin.php
index ca6133d..661f948 100644
--- a/lib/Settings/Admin.php
+++ b/lib/Settings/Admin.php
@@ -1,5 +1,15 @@
+ * @license https://www.gnu.org/licenses/agpl-3.0.html AGPL-3.0-or-later
+ * @link https://github.com/ConductionNL/nldesign
+ */
+
declare(strict_types=1);
namespace OCA\NLDesign\Settings;
@@ -10,56 +20,103 @@
use OCP\IL10N;
use OCP\Settings\ISettings;
-class Admin implements ISettings {
- private IConfig $config;
- private IL10N $l;
+/**
+ * Admin settings form for NL Design.
+ *
+ * Provides the configuration interface for selecting design token sets.
+ */
+class Admin implements ISettings
+{
+ private IConfig $config;
+ private IL10N $l;
+
+ /**
+ * Constructor.
+ *
+ * @param IConfig $config The config service.
+ * @param IL10N $l The localization service.
+ */
+ public function __construct(IConfig $config, IL10N $l)
+ {
+ $this->config = $config;
+ $this->l = $l;
+ }
+
+ /**
+ * Get the settings form.
+ *
+ * @return TemplateResponse The settings form template.
+ */
+ public function getForm(): TemplateResponse
+ {
+ $tokenSets = [
+ 'rijkshuisstijl' => [
+ 'name' => 'Rijkshuisstijl',
+ 'description' => $this->l->t('Dutch national government (Rijksoverheid)'),
+ ],
+ 'utrecht' => [
+ 'name' => 'Gemeente Utrecht',
+ 'description' => $this->l->t('Municipality of Utrecht'),
+ ],
+ 'amsterdam' => [
+ 'name' => 'Gemeente Amsterdam',
+ 'description' => $this->l->t('Municipality of Amsterdam'),
+ ],
+ 'denhaag' => [
+ 'name' => 'Gemeente Den Haag',
+ 'description' => $this->l->t('Municipality of The Hague'),
+ ],
+ 'rotterdam' => [
+ 'name' => 'Gemeente Rotterdam',
+ 'description' => $this->l->t('Municipality of Rotterdam'),
+ ],
+ ];
- public function __construct(IConfig $config, IL10N $l) {
- $this->config = $config;
- $this->l = $l;
- }
+ $currentTokenSet = $this->config->getAppValue(
+ Application::APP_ID,
+ 'token_set',
+ 'rijkshuisstijl'
+ );
- public function getForm(): TemplateResponse {
- $tokenSets = [
- 'rijkshuisstijl' => [
- 'name' => 'Rijkshuisstijl',
- 'description' => $this->l->t('Dutch national government (Rijksoverheid)'),
- ],
- 'utrecht' => [
- 'name' => 'Gemeente Utrecht',
- 'description' => $this->l->t('Municipality of Utrecht'),
- ],
- 'amsterdam' => [
- 'name' => 'Gemeente Amsterdam',
- 'description' => $this->l->t('Municipality of Amsterdam'),
- ],
- 'denhaag' => [
- 'name' => 'Gemeente Den Haag',
- 'description' => $this->l->t('Municipality of The Hague'),
- ],
- 'rotterdam' => [
- 'name' => 'Gemeente Rotterdam',
- 'description' => $this->l->t('Municipality of Rotterdam'),
- ],
- ];
+ $hideSlogan = $this->config->getAppValue(
+ Application::APP_ID,
+ 'hide_slogan',
+ '0'
+ ) === '1';
- $currentTokenSet = $this->config->getAppValue(
- Application::APP_ID,
- 'token_set',
- 'rijkshuisstijl'
- );
+ $showMenuLabels = $this->config->getAppValue(
+ Application::APP_ID,
+ 'show_menu_labels',
+ '0'
+ ) === '1';
- return new TemplateResponse(Application::APP_ID, 'settings/admin', [
- 'tokenSets' => $tokenSets,
- 'currentTokenSet' => $currentTokenSet,
- ]);
- }
+ return new TemplateResponse(
+ Application::APP_ID, 'settings/admin', [
+ 'tokenSets' => $tokenSets,
+ 'currentTokenSet' => $currentTokenSet,
+ 'hideSlogan' => $hideSlogan,
+ 'showMenuLabels' => $showMenuLabels,
+ ]
+ );
+ }
- public function getSection(): string {
- return 'theming';
- }
+ /**
+ * Get the settings section identifier.
+ *
+ * @return string The section identifier (theming).
+ */
+ public function getSection(): string
+ {
+ return 'theming';
+ }
- public function getPriority(): int {
- return 50;
- }
+ /**
+ * Get the priority for ordering in the settings menu.
+ *
+ * @return int The priority value (lower = higher priority).
+ */
+ public function getPriority(): int
+ {
+ return 50;
+ }
}
diff --git a/package.json b/package.json
index 7ef32c1..eeaafc7 100644
--- a/package.json
+++ b/package.json
@@ -20,6 +20,7 @@
"build": "npm run build:fonts && npm run build:icons",
"build:fonts": "node scripts/build-fonts.js",
"build:icons": "node scripts/build-icons.js",
+ "lint": "echo 'No JavaScript to lint - nldesign is CSS/PHP only'",
"test": "echo 'No tests yet'"
},
"dependencies": {
diff --git a/scripts/build-fonts.js b/scripts/build-fonts.js
old mode 100755
new mode 100644
index e1b7519..9727ff7
--- a/scripts/build-fonts.js
+++ b/scripts/build-fonts.js
@@ -12,14 +12,43 @@ const path = require('path');
const FONTS_DIR = path.join(__dirname, '..', 'css', 'fonts');
const FONTS_CSS = path.join(__dirname, '..', 'css', 'fonts.css');
+const FONTSOURCE_DIR = path.join(__dirname, '..', 'node_modules', '@fontsource', 'fira-sans', 'files');
-// Create fonts directory if it doesn't exist
+// Create fonts directory if it doesn't exist.
if (!fs.existsSync(FONTS_DIR)) {
fs.mkdirSync(FONTS_DIR, { recursive: true });
console.log('β Created css/fonts directory');
}
-// Generate fonts.css
+// Copy font files from node_modules.
+const fontFiles = [
+ 'fira-sans-latin-400-normal.woff2',
+ 'fira-sans-latin-400-normal.woff',
+ 'fira-sans-latin-400-italic.woff2',
+ 'fira-sans-latin-400-italic.woff',
+ 'fira-sans-latin-700-normal.woff2',
+ 'fira-sans-latin-700-normal.woff',
+ 'fira-sans-latin-700-italic.woff2',
+ 'fira-sans-latin-700-italic.woff',
+];
+
+let copiedCount = 0;
+if (fs.existsSync(FONTSOURCE_DIR)) {
+ fontFiles.forEach(file => {
+ const sourcePath = path.join(FONTSOURCE_DIR, file);
+ const destPath = path.join(FONTS_DIR, file);
+ if (fs.existsSync(sourcePath)) {
+ fs.copyFileSync(sourcePath, destPath);
+ copiedCount++;
+ }
+ });
+ console.log(`β Copied ${copiedCount} font files to css/fonts/`);
+} else {
+ console.log('β Warning: @fontsource/fira-sans not found in node_modules');
+ console.log(' Run: npm install');
+}
+
+// Generate fonts.css with correct paths.
const fontsCss = `/**
* Fira Sans Fonts
*
@@ -27,18 +56,11 @@ const fontsCss = `/**
* from @rijkshuisstijl-community/font package
*/
-/* Import Fira Sans from fontsource */
-@import url('~@fontsource/fira-sans/400.css');
-@import url('~@fontsource/fira-sans/400-italic.css');
-@import url('~@fontsource/fira-sans/700.css');
-@import url('~@fontsource/fira-sans/700-italic.css');
-
-/* Fallback for direct file access */
@font-face {
font-family: 'Fira Sans';
src: local('Fira Sans'),
- url('../fonts/fira-sans-400-normal.woff2') format('woff2'),
- url('../fonts/fira-sans-400-normal.woff') format('woff');
+ url('fonts/fira-sans-latin-400-normal.woff2') format('woff2'),
+ url('fonts/fira-sans-latin-400-normal.woff') format('woff');
font-weight: 400;
font-style: normal;
font-display: swap;
@@ -47,8 +69,8 @@ const fontsCss = `/**
@font-face {
font-family: 'Fira Sans';
src: local('Fira Sans Italic'),
- url('../fonts/fira-sans-400-italic.woff2') format('woff2'),
- url('../fonts/fira-sans-400-italic.woff') format('woff');
+ url('fonts/fira-sans-latin-400-italic.woff2') format('woff2'),
+ url('fonts/fira-sans-latin-400-italic.woff') format('woff');
font-weight: 400;
font-style: italic;
font-display: swap;
@@ -57,8 +79,8 @@ const fontsCss = `/**
@font-face {
font-family: 'Fira Sans';
src: local('Fira Sans Bold'),
- url('../fonts/fira-sans-700-normal.woff2') format('woff2'),
- url('../fonts/fira-sans-700-normal.woff') format('woff');
+ url('fonts/fira-sans-latin-700-normal.woff2') format('woff2'),
+ url('fonts/fira-sans-latin-700-normal.woff') format('woff');
font-weight: 700;
font-style: normal;
font-display: swap;
@@ -67,8 +89,8 @@ const fontsCss = `/**
@font-face {
font-family: 'Fira Sans';
src: local('Fira Sans Bold Italic'),
- url('../fonts/fira-sans-700-italic.woff2') format('woff2'),
- url('../fonts/fira-sans-700-italic.woff') format('woff');
+ url('fonts/fira-sans-latin-700-italic.woff2') format('woff2'),
+ url('fonts/fira-sans-latin-700-italic.woff') format('woff');
font-weight: 700;
font-style: italic;
font-display: swap;
@@ -79,5 +101,5 @@ fs.writeFileSync(FONTS_CSS, fontsCss);
console.log('β Generated css/fonts.css');
console.log('\nβ
Font build complete!');
-console.log('\nNote: Fonts are loaded via CDN from @fontsource/fira-sans');
-console.log('To use local files, copy font files from node_modules/@fontsource/fira-sans/files/ to css/fonts/');
+console.log(` ${copiedCount} font files copied to css/fonts/`);
+
diff --git a/scripts/build-tokens.js b/scripts/build-tokens.js
old mode 100755
new mode 100644
diff --git a/templates/settings/admin.php b/templates/settings/admin.php
index c2f499d..baa8cc6 100644
--- a/templates/settings/admin.php
+++ b/templates/settings/admin.php
@@ -32,6 +32,30 @@
+
+
+ checked>
+
+
+
+
+
+ checked>
+
+
+