diff --git a/CHANGELOG.md b/CHANGELOG.md
index 16129da..2bb2286 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,8 @@ und dieses Projekt folgt [Semantic Versioning](https://semver.org/lang/de/).
## [Unveröffentlicht]
### Hinzugefügt
+- Marken-Dateien nach Vorgabe unter `public/brand/`: Symbol (dunkel/hell), kleines Icon, Avatar, Logo (dunkel/hell), Banner 1280×640, Favicons, Apple-Touch- und Manifest-Icons; erzeugt mit `pnpm marke` (#017)
+- Web-App-Manifest; Favicon passt sich hellem und dunklem Browser an (#017)
### Geändert
diff --git a/docs/issues/017-branding.md b/docs/issues/017-branding.md
index 57e2ae4..08ba02a 100644
--- a/docs/issues/017-branding.md
+++ b/docs/issues/017-branding.md
@@ -1,5 +1,5 @@
# #017 Branding-Assets
-**Status:** offen
+**Status:** erledigt
**Meilenstein:** v0.6.0
**Labels:** design
**Geschätzt:** 4 h **Tatsächlich:** –
@@ -8,14 +8,21 @@
Alle Logo-Varianten, Banner, Favicons, Manifest-Icons nach Vorgabe.
## Aufgaben
-- [ ] symbol(-light), icon-small, avatar, logo(-light), banner 1280×640
-- [ ] Favicon 16/32, Apple-Touch-Icon, Manifest-Icons per Skript aus SVG
+- [x] symbol(-light), icon-small, avatar, logo(-light), banner 1280×640 (+ banner.png für die Social Preview)
+- [x] Favicon 16/32, Apple-Touch-Icon, Manifest-Icons (192, 512, maskierbar) per Skript aus SVG (`pnpm marke`)
## Akzeptanzkriterien
-- [ ] Darstellung in beiden Themes geprüft
+- [x] Darstellung in beiden Themes geprüft (Vorschau-Tafel dunkel/hell, Favicons auf hellem und dunklem Tab)
## Abhängigkeiten
#001
## Notizen / Entscheidungen
-–
+- **Texte als Pfade:** Wortmarke und Banner sehen überall gleich aus, auch ohne installierte Schrift.
+ Die TTFs (JetBrains Mono 2.304, Space Grotesk 2.0.0, OFL) lädt `pnpm marke` mit SHA-256-Prüfung
+ nach `node_modules/.cache`; eingecheckt werden nur die Ergebnisse.
+- opentype.js 1.3.5 kennt die Ligatur-Regeln von JetBrains Mono nicht und schreibt in `toPathData`
+ gelegentlich „NaN“ – deshalb Glyph für Glyph gesetzt und Pfaddaten selbst geschrieben.
+- `icon-small.svg` passt sich per `prefers-color-scheme` an; die PNG-Favicons sind dicke Linien auf
+ Teal, weil 16 px mit den dünnen Linien des Symbols nicht lesbar waren.
+- Unterzeile im Banner 32 statt 34 px – sonst breiter als die Ränder.
diff --git a/docs/issues/README.md b/docs/issues/README.md
index f339e75..65ffa88 100644
--- a/docs/issues/README.md
+++ b/docs/issues/README.md
@@ -20,7 +20,7 @@ Details und Architektur: [../architektur.md](../architektur.md). Zeiten werden a
| [#014](014-verlauf.md) | Verlauf und Wiederholen | erledigt | v0.5.0 | 4 h | 1 h 21 min |
| [#015](015-rollen-audit.md) | Rollen, Benutzer, Tokens, Audit-Log | erledigt | v0.5.0 | 6 h | 0 h 10 min |
| [#016](016-oidc.md) | Anmeldung per OIDC | erledigt | v0.5.0 | 4 h | 0 h 08 min |
-| [#017](017-branding.md) | Branding-Assets | offen | v0.6.0 | 4 h | – |
+| [#017](017-branding.md) | Branding-Assets | erledigt | v0.6.0 | 4 h | – |
| [#018](018-readme.md) | README und Dokumentation | offen | v0.6.0 | 4 h | – |
| [#019](019-installer.md) | Docker-Image und LXC-Installer | offen | v0.6.0 | 6 h | – |
| [#020](020-feldtest.md) | Feldtest 8.x und 9.x, Version 1.0.0 | offen | v1.0.0 | 6 h | – |
diff --git a/package.json b/package.json
index 9bcd679..9a0e3bc 100644
--- a/package.json
+++ b/package.json
@@ -28,7 +28,8 @@
"typecheck": "tsc --noEmit",
"worker": "tsx src/worker/main.ts",
"openapi": "tsx scripts/openapi.ts",
- "cli:pruefen": "scripts/cli-pruefen.sh"
+ "cli:pruefen": "scripts/cli-pruefen.sh",
+ "marke": "tsx scripts/marke-erzeugen.ts"
},
"dependencies": {
"@fontsource-variable/jetbrains-mono": "^5.3.0",
@@ -55,8 +56,10 @@
"@commitlint/cli": "^21.2.3",
"@commitlint/config-conventional": "^21.2.3",
"@playwright/test": "1.63.0",
+ "@resvg/resvg-js": "2.6.2",
"@tailwindcss/postcss": "^4.3.3",
"@types/node": "^24.13.6",
+ "@types/opentype.js": "^1.3.10",
"@types/pg": "^8.23.1",
"@types/react": "^19.3.0",
"@types/react-dom": "^19.3.0",
@@ -68,6 +71,7 @@
"eslint-config-prettier": "^10.1.8",
"husky": "^9.1.7",
"lint-staged": "^17.5.1",
+ "opentype.js": "1.3.5",
"prettier": "^3.9.9",
"prisma": "7.10.0",
"tailwindcss": "^4.3.3",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index cfa95f3..6513595 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -233,12 +233,18 @@ importers:
'@playwright/test':
specifier: 1.63.0
version: 1.63.0
+ '@resvg/resvg-js':
+ specifier: 2.6.2
+ version: 2.6.2
'@tailwindcss/postcss':
specifier: ^4.3.3
version: 4.3.3
'@types/node':
specifier: ^24.13.6
version: 24.19.2
+ '@types/opentype.js':
+ specifier: ^1.3.10
+ version: 1.3.10
'@types/pg':
specifier: ^8.23.1
version: 8.23.1
@@ -272,6 +278,9 @@ importers:
lint-staged:
specifier: ^17.5.1
version: 17.6.0
+ opentype.js:
+ specifier: 1.3.5
+ version: 1.3.5
prettier:
specifier: ^3.9.9
version: 3.9.10
@@ -1357,6 +1366,86 @@ packages:
'@types/react':
optional: true
+ '@resvg/resvg-js-android-arm-eabi@2.6.2':
+ resolution: {integrity: sha512-FrJibrAk6v29eabIPgcTUMPXiEz8ssrAk7TXxsiZzww9UTQ1Z5KAbFJs+Z0Ez+VZTYgnE5IQJqBcoSiMebtPHA==}
+ engines: {node: '>= 10'}
+ cpu: [arm]
+ os: [android]
+
+ '@resvg/resvg-js-android-arm64@2.6.2':
+ resolution: {integrity: sha512-VcOKezEhm2VqzXpcIJoITuvUS/fcjIw5NA/w3tjzWyzmvoCdd+QXIqy3FBGulWdClvp4g+IfUemigrkLThSjAQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [android]
+
+ '@resvg/resvg-js-darwin-arm64@2.6.2':
+ resolution: {integrity: sha512-nmok2LnAd6nLUKI16aEB9ydMC6Lidiiq2m1nEBDR1LaaP7FGs4AJ90qDraxX+CWlVuRlvNjyYJTNv8qFjtL9+A==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@resvg/resvg-js-darwin-x64@2.6.2':
+ resolution: {integrity: sha512-GInyZLjgWDfsVT6+SHxQVRwNzV0AuA1uqGsOAW+0th56J7Nh6bHHKXHBWzUrihxMetcFDmQMAX1tZ1fZDYSRsw==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@resvg/resvg-js-linux-arm-gnueabihf@2.6.2':
+ resolution: {integrity: sha512-YIV3u/R9zJbpqTTNwTZM5/ocWetDKGsro0SWp70eGEM9eV2MerWyBRZnQIgzU3YBnSBQ1RcxRZvY/UxwESfZIw==}
+ engines: {node: '>= 10'}
+ cpu: [arm]
+ os: [linux]
+
+ '@resvg/resvg-js-linux-arm64-gnu@2.6.2':
+ resolution: {integrity: sha512-zc2BlJSim7YR4FZDQ8OUoJg5holYzdiYMeobb9pJuGDidGL9KZUv7SbiD4E8oZogtYY42UZEap7dqkkYuA91pg==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@resvg/resvg-js-linux-arm64-musl@2.6.2':
+ resolution: {integrity: sha512-3h3dLPWNgSsD4lQBJPb4f+kvdOSJHa5PjTYVsWHxLUzH4IFTJUAnmuWpw4KqyQ3NA5QCyhw4TWgxk3jRkQxEKg==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@resvg/resvg-js-linux-x64-gnu@2.6.2':
+ resolution: {integrity: sha512-IVUe+ckIerA7xMZ50duAZzwf1U7khQe2E0QpUxu5MBJNao5RqC0zwV/Zm965vw6D3gGFUl7j4m+oJjubBVoftw==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@resvg/resvg-js-linux-x64-musl@2.6.2':
+ resolution: {integrity: sha512-UOf83vqTzoYQO9SZ0fPl2ZIFtNIz/Rr/y+7X8XRX1ZnBYsQ/tTb+cj9TE+KHOdmlTFBxhYzVkP2lRByCzqi4jQ==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@resvg/resvg-js-win32-arm64-msvc@2.6.2':
+ resolution: {integrity: sha512-7C/RSgCa+7vqZ7qAbItfiaAWhyRSoD4l4BQAbVDqRRsRgY+S+hgS3in0Rxr7IorKUpGE69X48q6/nOAuTJQxeQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@resvg/resvg-js-win32-ia32-msvc@2.6.2':
+ resolution: {integrity: sha512-har4aPAlvjnLcil40AC77YDIk6loMawuJwFINEM7n0pZviwMkMvjb2W5ZirsNOZY4aDbo5tLx0wNMREp5Brk+w==}
+ engines: {node: '>= 10'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@resvg/resvg-js-win32-x64-msvc@2.6.2':
+ resolution: {integrity: sha512-ZXtYhtUr5SSaBrUDq7DiyjOFJqBVL/dOBN7N/qmi/pO0IgiWW/f/ue3nbvu9joWE5aAKDoIzy/CxsY0suwGosQ==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [win32]
+
+ '@resvg/resvg-js@2.6.2':
+ resolution: {integrity: sha512-xBaJish5OeGmniDj9cW5PRa/PtmuVU3ziqrbr5xJj901ZDN4TosrVaNZpEiLZAxdfnhAe7uQ7QFWfjPe9d9K2Q==}
+ engines: {node: '>= 10'}
+
'@rolldown/binding-android-arm-eabi@1.2.13':
resolution: {integrity: sha512-Vu/NICrgdRvUOeaJZIyB2FP63s6fO/4ud5l8dXmxDx9yDyn+mN0y+gsvsVviSli+T361Aqo0h46EuMoobrwawA==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -1628,6 +1717,9 @@ packages:
'@types/node@24.19.2':
resolution: {integrity: sha512-93d49wCWJkCyN87MxLpNN6KXPCwFw7bVUg79mGUV1OS40JGWwVCr8n61DXCSqz7CV6/VfPkb7xec7uP2M+QHWQ==}
+ '@types/opentype.js@1.3.10':
+ resolution: {integrity: sha512-F67EFyk6j02okHz5JCgata3ZRAcZi9GLnzmkHw/rzJq3OCc8/ZVdoKrxMTYjcQP6IYHGBz2cav1cpzkOkPiPCQ==}
+
'@types/pg@8.23.1':
resolution: {integrity: sha512-fKVHpikPdg4GKks3JuLEhvwSyvwzF23hnabPy6DD8ljVbC7+6J5dQzdv4arV6jqq57djnMgs1HKBxX4P8aBI3A==}
@@ -3235,6 +3327,11 @@ packages:
openid-client@6.8.8:
resolution: {integrity: sha512-ZsucJA5Ad04Uv7YN4ql+s4GXNmb9uAYQwyJTsJx7CH/MX3JZioLE2pVKi1SC+YrbSLA4Px3Gi30dMjgOZtb6pA==}
+ opentype.js@1.3.5:
+ resolution: {integrity: sha512-thKDiELidAApOvXlncrpwDZKJCa9fXLEKM4+FoEWI+qTLDeNb+h7EkN+8a7KQODsB1GZ+Exz9KknkoPrEdXZDw==}
+ deprecated: This version should be ignored, it was meant to be published as 2.0.0 but got published as 1.3.5 by mistake. As of May 6th 2026 the latest v1 release is v1.3.4
+ hasBin: true
+
optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'}
@@ -4888,6 +4985,57 @@ snapshots:
optionalDependencies:
'@types/react': 19.3.0
+ '@resvg/resvg-js-android-arm-eabi@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-android-arm64@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-darwin-arm64@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-darwin-x64@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-linux-arm-gnueabihf@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-linux-arm64-gnu@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-linux-arm64-musl@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-linux-x64-gnu@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-linux-x64-musl@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-win32-arm64-msvc@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-win32-ia32-msvc@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-win32-x64-msvc@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js@2.6.2':
+ optionalDependencies:
+ '@resvg/resvg-js-android-arm-eabi': 2.6.2
+ '@resvg/resvg-js-android-arm64': 2.6.2
+ '@resvg/resvg-js-darwin-arm64': 2.6.2
+ '@resvg/resvg-js-darwin-x64': 2.6.2
+ '@resvg/resvg-js-linux-arm-gnueabihf': 2.6.2
+ '@resvg/resvg-js-linux-arm64-gnu': 2.6.2
+ '@resvg/resvg-js-linux-arm64-musl': 2.6.2
+ '@resvg/resvg-js-linux-x64-gnu': 2.6.2
+ '@resvg/resvg-js-linux-x64-musl': 2.6.2
+ '@resvg/resvg-js-win32-arm64-msvc': 2.6.2
+ '@resvg/resvg-js-win32-ia32-msvc': 2.6.2
+ '@resvg/resvg-js-win32-x64-msvc': 2.6.2
+
'@rolldown/binding-android-arm-eabi@1.2.13':
optional: true
@@ -5078,6 +5226,8 @@ snapshots:
dependencies:
undici-types: 7.24.6
+ '@types/opentype.js@1.3.10': {}
+
'@types/pg@8.23.1':
dependencies:
'@types/node': 24.19.2
@@ -6848,6 +6998,8 @@ snapshots:
jose: 6.2.12
oauth4webapi: 3.8.8
+ opentype.js@1.3.5: {}
+
optionator@0.9.4:
dependencies:
deep-is: 0.1.4
diff --git a/public/brand/apple-touch-icon.png b/public/brand/apple-touch-icon.png
new file mode 100644
index 0000000..db2a548
Binary files /dev/null and b/public/brand/apple-touch-icon.png differ
diff --git a/public/brand/avatar.svg b/public/brand/avatar.svg
new file mode 100644
index 0000000..6750e24
--- /dev/null
+++ b/public/brand/avatar.svg
@@ -0,0 +1,8 @@
+
diff --git a/public/brand/banner.png b/public/brand/banner.png
new file mode 100644
index 0000000..3cb9615
Binary files /dev/null and b/public/brand/banner.png differ
diff --git a/public/brand/banner.svg b/public/brand/banner.svg
new file mode 100644
index 0000000..e16662d
--- /dev/null
+++ b/public/brand/banner.svg
@@ -0,0 +1,29 @@
+
diff --git a/public/brand/favicon-16.png b/public/brand/favicon-16.png
new file mode 100644
index 0000000..7dc0cb0
Binary files /dev/null and b/public/brand/favicon-16.png differ
diff --git a/public/brand/favicon-32.png b/public/brand/favicon-32.png
new file mode 100644
index 0000000..2a31445
Binary files /dev/null and b/public/brand/favicon-32.png differ
diff --git a/public/brand/icon-192.png b/public/brand/icon-192.png
new file mode 100644
index 0000000..c2a1581
Binary files /dev/null and b/public/brand/icon-192.png differ
diff --git a/public/brand/icon-512.png b/public/brand/icon-512.png
new file mode 100644
index 0000000..b6ebe9d
Binary files /dev/null and b/public/brand/icon-512.png differ
diff --git a/public/brand/icon-maskable-512.png b/public/brand/icon-maskable-512.png
new file mode 100644
index 0000000..0c187a2
Binary files /dev/null and b/public/brand/icon-maskable-512.png differ
diff --git a/public/brand/icon-small.svg b/public/brand/icon-small.svg
new file mode 100644
index 0000000..e2edfb2
--- /dev/null
+++ b/public/brand/icon-small.svg
@@ -0,0 +1,5 @@
+
diff --git a/public/brand/logo-light.svg b/public/brand/logo-light.svg
new file mode 100644
index 0000000..a176417
--- /dev/null
+++ b/public/brand/logo-light.svg
@@ -0,0 +1,8 @@
+
diff --git a/public/brand/logo.svg b/public/brand/logo.svg
new file mode 100644
index 0000000..7e0e353
--- /dev/null
+++ b/public/brand/logo.svg
@@ -0,0 +1,8 @@
+
diff --git a/public/brand/symbol-light.svg b/public/brand/symbol-light.svg
new file mode 100644
index 0000000..8bd3ccf
--- /dev/null
+++ b/public/brand/symbol-light.svg
@@ -0,0 +1,5 @@
+
diff --git a/public/brand/symbol.svg b/public/brand/symbol.svg
index 14aae88..8297779 100644
--- a/public/brand/symbol.svg
+++ b/public/brand/symbol.svg
@@ -1,4 +1,4 @@
-