Add BMAD planning artifacts: brief, PRD, architecture, UX, spec, tickets

Includes the BMAD install config, the initiative ticket tree, the portal
context-resources scripts, and the .gitignore change that tracks BMAD files
(generated _bmad/render/ is ignored).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sk72AxLwQnYttF6VFSxQLt
This commit is contained in:
Ebbe Baß
2026-10-15 13:33:02 +02:00
co-authored by Claude Sonnet 5.5
parent 97b2a95062
commit 24de5878ed
60 changed files with 13647 additions and 6 deletions
@@ -0,0 +1,30 @@
---
topic: ping-mee Self-Service Portal UX
updated: 2026-10-15T11:57
---
- (event) Create intent. Source candidates (paths only): brief, brief addendum, prd, prd addendum, ARCHITECTURE-SPINE (AD-16 strict CSP, vendored Fluent web components 3.x, server-rendered Django, de+en i18n).
- (decision) Feel (user words): a utility, simple and straight to the point; users are NOT tech savvy; imagined like the M365 admin center: clean, simple, 'just a big page for settings'; not too much for the user; settings intuitive and informative.
- (decision) Themes: light AND dark, default follows system theme (prefers-color-scheme), user can override. Form factors: desktop AND mobile both must work (responsive web).
- (assumption) Working mode not stated by user - proceeding in COACHING path (suggested); stakes = internal tool.
- (decision) IA: option B (short left menu, one topic per page) PLUS a big dashboard as the landing page showing announcements and some account information.
- (decision) 'Informative' = transparency about what each setting does (explain effect of each field/action, e.g. password rules before typing, picture 'updates within ~2h'/pending).
- (decision) Voice: German formal (Sie) by default with a setting to switch to informal (du). Language auto-detected from browser language, switchable.
- (decision) Colour: Microsoft blue by default; colour must be changeable in settings. Theme (light/dark/system), formal/informal, and language switch live in ONE menu opened from the profile picture in the upper right corner; stored PER DEVICE.
- (assumption) To confirm: is the colour change an Admin (global, PRD FR-22) setting or per-user? Is formal/informal German-only? Dashboard 'account info' contents.
- (decision) Accent colour is PORTAL-WIDE (Admin sets it; matches PRD FR-22); user menu keeps theme/language/formality only. Formal/informal is a GERMAN-ONLY setting (hidden when language is English).
- (decision) Dashboard account card also shows 'Password last changed on ...' (from AD pwdLastSet). No further pages wanted beyond the IA table.
- (assumption) UNANSWERED: should informal setting apply to e-mails? Assumed: e-mails use the portal-wide default register (per-device choice cannot reach server-sent mail). Also assumed default brand blue = Fluent 2 web components default; verify at scaffold.
- (event) Rendered 3 key-screen mocks in .working/: key-dashboard.html (desktop light + menu open, phone dark), key-password.html, key-picture-pending.html. German formal copy. Password rules in the mock are SAMPLE text; real rules need domain policy (new requirement: portal reads AD password policy).
- (assumption) Mock layout assumptions: top bar in brand blue (dark: deep navy), left nav 6 items (collapses to burger on phone), banner for active announcement (left accent bar by severity), card grid on dashboard (stacked on phone).
- (decision) Mock review feedback: look approved ('really good', M365-admin-centre feel right); wording 'perfect'; dashboard 'clean'. Defect: phone dashboard cut off on right -> fixed (main min-width:0, rows stack on phone, text wraps).
- (decision) States, wording rules and accessibility floor drafted by facilitator accepted by user ('no' changes): saved/error/too-many-attempts/pending/loading/empty/unavailable states; plain-language, no jargon (no LDAP/Verzeichnis/Token); strings in one translation file x3 (de-Sie, de-du, en); WCAG 2.1 AA, keyboard, skip link, icon+text not colour alone, 44px touch targets, errors linked to fields and announced.
- (decision) Accent colour check: portal computes contrast for the chosen colour (light and dark theme) and shows a CHECK result plus a WARNING when below AA; Admin may still save (warn, not block).
- (decision) Password rules: portal READS the policy from AD and lists it before typing (NEW requirement - affects PRD FR-5 and ARCHITECTURE-SPINE AD-2 read allow-list; consider Fine-Grained Password Policy msDS-ResultantPSO).
- (decision) User wants MOCKS of Publisher and Admin screens (announcement editor, admin appearance, status, audit log) to give opinion.
- (event) Rendered 4 admin/publisher mocks in .working/: key-announcement-editor, key-admin-appearance (contrast check+warning, computed 1.41:1 white vs #FFD60A, 14.02:1 dark text), key-admin-status (backup age, failed mail, jobs), key-admin-audit. Nav gains groups 'Veröffentlichen' (Publisher) and 'Administration' (Darstellung, Einstellungen, Protokoll, Status). Awaiting user opinion.
- (decision) Mock feedback: admin/publisher mocks approved otherwise ('cannot add anything'). CHANGE 1: announcement body uses a WYSIWYG editor (toolbar, formatted as you type) instead of visible Markdown; stored format stays Markdown (editor converts), server still sanitizes on render; e-mail keeps plain-text part. Affects PRD FR-16 wording + ARCHITECTURE-SPINE AD-16; editor library undecided (needs web verification; must be vendored, CSP-safe).
- (decision) CHANGE 2: Importance (severity) is ALWAYS set, not optional: default Information, shown in the main card. Optional card keeps only downtime window, expiry, schedule. Affects PRD FR-17 (severity was listed optional).
- (event) Mockups promoted from .working/ to mockups/ (7 files). All 7 IA surfaces with a layout driver are mocked; spine-only: Profil, Benachrichtigungen, Ankündigungen (read list), confirm/undo pages, Einstellungen.
- (event) spines finalized
@@ -0,0 +1,183 @@
---
name: ping-mee Portal
description: Calm, utility-style self-service portal for a homelab, in the spirit of the Microsoft 365 admin center. Fluent 2 web components; this DESIGN.md specifies the brand-layer delta and the portal's own components.
status: final
updated: 2026-10-15
sources:
- _bmad-output/prds/prd-ping-mee-self-service-platform-2026-10-15/prd.md
- _bmad-output/architecture/architecture-ping-mee-self-service-platform-2026-10-15/ARCHITECTURE-SPINE.md
colors:
# Fluent 2 neutrals are inherited. Only the brand and the portal's semantic colours are listed.
# Brand is portal-wide and Admin-changeable; this is the default (Fluent 2 default brand blue). [ASSUMPTION: verify against @fluentui/web-components 3.x at scaffold]
brand: '#0F6CBD'
on-brand: '#FFFFFF'
brand-dark: '#479EF5'
on-brand-dark: '#0A0A0A'
top-bar-dark: '#0F2A43'
background: '#FAFAFA'
surface: '#FFFFFF'
nav-surface: '#F5F5F5'
text: '#242424'
text-secondary: '#616161'
line: '#E0E0E0'
background-dark: '#1F1F1F'
surface-dark: '#292929'
nav-surface-dark: '#242424'
text-dark: '#FFFFFF'
text-secondary-dark: '#C8C8C8'
line-dark: '#3D3D3D'
severity-info: '#0F6CBD'
severity-warning: '#F7630C'
severity-outage: '#B10E1C'
severity-outage-dark: '#FF99A4'
success: '#107C10'
success-dark: '#6CCB5F'
pending-surface: '#FFF4CE'
pending-text: '#5C4400'
pending-surface-dark: '#4A3B00'
pending-text-dark: '#FFE9A8'
typography:
# System font stack only: the security policy forbids external fonts.
family:
fontFamily: '"Segoe UI Variable", "Segoe UI", system-ui, sans-serif'
page-title:
fontSize: 24px
fontWeight: '600'
lineHeight: '1.25'
page-title-phone:
fontSize: 20px
fontWeight: '600'
lineHeight: '1.25'
card-title:
fontSize: 16px
fontWeight: '600'
body:
fontSize: 14px
fontWeight: '400'
lineHeight: '1.45'
label:
fontSize: 14px
fontWeight: '600'
small:
fontSize: 13px
hint:
fontSize: 12px
mono-editor:
fontFamily: 'Consolas, monospace'
fontSize: 13px
rounded:
sm: 4px
md: 6px
lg: 8px
pill: 10px
full: 9999px
spacing:
card-padding: 16px 18px
card-gap: 14px
page-padding: 24px 28px
page-padding-phone: 16px
nav-width: 220px
top-bar-height: 48px
touch-target-min: 44px
components:
top-bar:
background: '{colors.brand}'
foreground: '{colors.on-brand}'
height: '{spacing.top-bar-height}'
top-bar-dark:
background: '{colors.top-bar-dark}'
foreground: '{colors.text-dark}'
nav-item:
foreground: '{colors.text}'
radius: '{rounded.sm}'
nav-item-active:
background: '{colors.surface}'
border: 'inset 3px left {colors.brand}'
fontWeight: '600'
card:
background: '{colors.surface}'
border: '1px solid {colors.line}'
radius: '{rounded.lg}'
padding: '{spacing.card-padding}'
banner:
radius: '{rounded.md}'
border: '1px solid, left 5px, colour by severity'
button-primary:
background: '{colors.brand}'
foreground: '{colors.on-brand}'
radius: '{rounded.sm}'
button-secondary:
background: 'transparent'
foreground: '{colors.text}'
border: '1px solid'
radius: '{rounded.sm}'
chip-pending:
background: '{colors.pending-surface}'
foreground: '{colors.pending-text}'
radius: '{rounded.pill}'
input:
border: '1px solid, bottom 2px'
radius: '{rounded.sm}'
avatar:
radius: '{rounded.full}'
---
## Brand & Style
A calm utility. The portal should feel like the Microsoft 365 admin center's settings pages: clean, plain and confident, one clear task per page, with nothing decorative. The people who use it are not technical, so the design earns trust through restraint and legibility, not through personality. It follows Fluent 2 so the controls behave the way these users already know from Microsoft products.
Light and dark are equal citizens. The portal follows the system theme by default and the user can override it per device. The accent colour is portal-wide and set by the Admin.
Mock-ups: `mockups/key-dashboard.html`, `mockups/key-password.html`, `mockups/key-picture-pending.html`, `mockups/key-announcement-editor.html`, `mockups/key-admin-appearance.html`, `mockups/key-admin-status.html`, `mockups/key-admin-audit.html`. The spine wins on conflict.
## Colors
- **`{colors.brand}`** is the one accent: top bar, primary buttons, active-nav marker and focus emphasis. It is Admin-changeable (FR-22). In dark mode the lighter `{colors.brand-dark}` is used with `{colors.on-brand-dark}` text.
- **Neutrals** (`background`, `surface`, `nav-surface`, `text`, `text-secondary`, `line`) carry almost everything; the dark variants have the `-dark` suffix. Cards sit on `{colors.surface}` above `{colors.background}`.
- **Severity colours** colour only the left edge and icon of an announcement banner: `{colors.severity-info}`, `{colors.severity-warning}`, `{colors.severity-outage}`. Colour is never the only signal; each severity also has an icon and a word.
- **Pending** uses `{colors.pending-surface}` and `{colors.pending-text}` for the "being updated" chip and nothing else.
- **Success** (`{colors.success}`) marks healthy job status and saved confirmations.
- **Contrast.** All text and control pairs meet WCAG 2.1 AA in both themes. The Admin colour check (EXPERIENCE.md, Admin Appearance) computes contrast for the chosen brand colour against `{colors.on-brand}` (light) and `{colors.on-brand-dark}` (dark) and warns when below 4.5:1.
## Typography
One family, the system stack `{typography.family}`, because the security policy allows no external fonts. A small ramp: `{typography.page-title}` for the page heading (`{typography.page-title-phone}` on phones), `{typography.card-title}` for cards, `{typography.body}` for text, `{typography.label}` for field labels, `{typography.small}` and `{typography.hint}` for explanations. The announcement editor's source view, if shown, uses `{typography.mono-editor}`. Sentence case everywhere; no all-caps except the small group headings in the menu.
## Layout & Spacing
Left menu (`{spacing.nav-width}`) plus a content area on desktop; the menu collapses behind a menu button on phones. Content is left-aligned in cards on a single column; two columns of cards only on the dashboard and the status page, stacked on phones. Page padding is `{spacing.page-padding}` (`{spacing.page-padding-phone}` on phones); cards are separated by `{spacing.card-gap}`. Lines of explanatory text stay below about 640 px wide. Touch targets are at least `{spacing.touch-target-min}` on phones. On phones, label/value rows stack and long values wrap, so nothing is cut off at the right edge.
## Elevation & Depth
Flat. Depth comes from a 1 px `{colors.line}` border and the surface/background step, not from shadows. The only shadows are on the profile menu (a popover) and on dialogs.
## Shapes
Small, even radii: `{rounded.sm}` for buttons and inputs, `{rounded.md}` for banners, `{rounded.lg}` for cards, `{rounded.pill}` for chips, `{rounded.full}` for avatars.
## Components
| Component | Visual spec |
|---|---|
| Top bar | `{components.top-bar}`: portal name at left, profile picture at right (opens the profile menu). On phones a menu button replaces the left menu. Dark mode uses `{components.top-bar-dark}`. |
| Left menu | Items `{components.nav-item}`; active item `{components.nav-item-active}`. Group headings "Veröffentlichen" and "Administration" appear only for Publishers and Admins, in small upper-case text. |
| Card | `{components.card}`; title `{typography.card-title}`. |
| Announcement banner | `{components.banner}`: left edge in the severity colour, icon, bold title, one or two lines of text. At most the active announcements, newest first. |
| Pending chip | `{components.chip-pending}` with the text "Wird aktualisiert". |
| Buttons | Primary `{components.button-primary}`; secondary `{components.button-secondary}`. One primary per card. |
| Fields | `{components.input}`; label above, hint below in `{typography.hint}`; error text below in the outage colour with an icon. |
| Profile menu | Popover from the avatar: name and username, theme (Hell, Dunkel, System), language (Deutsch, English), address form (Sie, Du; German only), sign-out. |
| Rich text editor | Toolbar above a text area that shows the result: bold, italic, heading, list, link. Same border as inputs. |
| Tables | Compact rows with `{colors.line}` separators, used for the audit log, job status and failed mail. |
| Filter chips | Pill buttons; the selected chip uses `{colors.brand}`. |
| Colour field (Admin) | Swatch, hex field, contrast table (light and dark), warning box. |
## Do's and Don'ts
- **Do** keep one primary action per card and say what it will do and what happens next.
- **Do** show state in words as well as colour.
- **Do** keep pages short; split a long page before adding scrolling sections.
- **Don't** add decoration, illustrations or animation beyond Fluent's own.
- **Don't** use more than the one brand accent.
- **Don't** hide a state in colour alone, or in hover.
- **Don't** load fonts, icons or scripts from the network.
@@ -0,0 +1,134 @@
---
name: ping-mee Portal
status: final
updated: 2026-10-15
sources:
- _bmad-output/prds/prd-ping-mee-self-service-platform-2026-10-15/prd.md
- _bmad-output/architecture/architecture-ping-mee-self-service-platform-2026-10-15/ARCHITECTURE-SPINE.md
---
# ping-mee Portal — Experience Spine
## Foundation
Responsive web, server-rendered pages. Desktop and phone both work. UI system: Fluent 2 web components (version 3), built into the image. `DESIGN.md` is the visual identity reference and names the brand layer; this spine is the behavior. Users are family and friends who are not technical, so every page does one job and explains it. Languages: German and English. German has two address forms, formal "Sie" (default) and informal "du".
## Information Architecture
| Surface | Reached from | Purpose | Who |
|---|---|---|---|
| Startseite (dashboard) | Landing page, menu | Active announcements, account card, last announcements | everyone |
| Profil | Menu, account card | Edit first name, last name, display name, e-mail, telephone, mobile | everyone |
| Profilbild | Menu | Upload, crop, remove; shows Pending | everyone |
| Passwort | Menu | Change password | everyone |
| Ankündigungen | Menu, dashboard | Read current and past announcements | everyone |
| Benachrichtigungen | Menu | Opt in or out of announcement e-mails | everyone |
| E-Mail bestätigen / rückgängig | Link in an e-mail | Confirm or undo an e-mail change | link holder |
| Ankündigungen verwalten | Menu group "Veröffentlichen" | List, write, schedule, edit, withdraw | Publishers |
| Darstellung | Menu group "Administration" | Accent colour with contrast check, background image | Admins |
| Einstellungen | same | Portal name, session limits, throttle limits, audit retention | Admins |
| Protokoll | same | Read the audit log | Admins |
| Status | same | Backup age, failed e-mails, background jobs | Admins |
| Profile menu | Avatar, upper right | Theme, language, address form (German only), sign-out | everyone |
On phones the left menu opens from a menu button. Dialogs never stack more than one level deep. Every stated need has a surface, and every surface is reached by a flow below.
→ Composition reference: `mockups/key-dashboard.html`, `mockups/key-password.html`, `mockups/key-picture-pending.html`, `mockups/key-announcement-editor.html`, `mockups/key-admin-appearance.html`, `mockups/key-admin-status.html`, `mockups/key-admin-audit.html`. Spine wins on conflict.
## Voice and Tone
Microcopy. Brand posture lives in `DESIGN.md`. All text is translatable and kept in one translation file with three registers: German formal, German informal, English. The e-mails use the portal-wide default register. [ASSUMPTION: a per-device choice cannot reach server-sent mail.]
| Do | Don't |
|---|---|
| "Ihre Änderungen wurden gespeichert." | "Operation erfolgreich." |
| "Ihr neues Bild erscheint innerhalb von etwa 2 Stunden." | "Der Sync-Task läuft periodisch." |
| "Zu viele Versuche. Bitte warten Sie einige Minuten." | "HTTP 429" |
| "Das ist gerade nicht möglich. Bitte versuchen Sie es später erneut." | Directory, LDAP, token or other system words |
| Say what the action does and what happens next. | Leave the user guessing after a click. |
| Short sentences, one idea each. | Long explanations before the form. |
The informal setting changes only the form of address ("Sie" becomes "du", with matching verbs and possessives) and applies only to German.
## Component Patterns
Behavioral. Visual specs live in `DESIGN.md.Components`.
| Component | Use | Behavioral rules |
|---|---|---|
| Announcement banner | Dashboard | Shows every active announcement, newest first, severity in colour, icon and word. Cannot be dismissed while active. |
| Account card | Dashboard | Shows picture (with Pending chip if waiting), name, username, e-mail, telephone, "Passwort zuletzt geändert" from AD. Link to Profil. |
| Form | Profil, Passwort, Profilbild | Explains the effect of the action above the first field. Validates on submit, shows errors next to the field and in a summary at the top. Primary button shows a spinner while working and is disabled. |
| Password rules | Passwort | The rules read from the domain's password policy are listed before the user types. [New requirement: the portal reads the policy from AD.] |
| Sensitive action | Passwort, E-Mail change | Asks for the current password in the same form. Never remembers it. |
| Picture editor | Profilbild | Choose a file, crop to a square and adjust in the browser, then save. Accepts JPEG, PNG, HEIC, WebP up to 10 MB; says why when it refuses a file. |
| Rich text editor | Ankündigungen verwalten | Toolbar: bold, italic, heading, list, link. The text shows as the finished result. Stored as Markdown, sanitized when shown. |
| Importance | Announcement editor | Always set; default "Information". Cannot be left empty. |
| Recipient summary | Announcement editor | States how many users get the e-mail and why others do not (opted out, no address). |
| Contrast check | Darstellung | Shows contrast for light and dark. Below 4.5:1 shows a warning; saving stays possible. |
| Profile menu | Every page | Theme, language, address form (shown only in German), sign-out. Choices are stored per device and apply immediately. |
| Filter chips | Protokoll | One selected at a time; "Alle" by default. |
## State Patterns
| State | Surface | Treatment |
|---|---|---|
| Saved | Any form | A success message at the top: what was saved and what happens next. |
| Validation error | Any form | Message under the field, linked to it, plus a summary; focus moves to the summary. |
| Password rejected by policy | Passwort | The reason in plain words, for example "Das Passwort ist zu kurz." Nothing is changed. |
| Too many attempts | Passwort, E-Mail change, links, upload | "Zu viele Versuche. Bitte warten Sie einige Minuten." No technical detail; the pause is not shown in numbers. |
| Pending | Profilbild, dashboard | Yellow chip "Wird aktualisiert" and the two-hour note. If it lasts longer than the configured time: "Das dauert länger als erwartet." |
| Loading | Buttons | Spinner on the button; the page stays usable. Cold page loads are plain server-rendered pages with no skeleton. |
| Empty | Ankündigungen, lists | A friendly line, for example "Derzeit gibt es keine Ankündigungen." |
| Unavailable | Any action needing AD, the share or mail | "Das ist gerade nicht möglich. Bitte versuchen Sie es später erneut." Nothing changes. |
| Session ended | Any page | The next action leads to sign-in with "Aus Sicherheitsgründen wurden Sie abgemeldet." [ASSUMPTION] |
| Not allowed | Publisher or Admin page for a normal user | The page does not exist for them: no menu entry; a direct address shows "Dazu fehlt Ihnen die Berechtigung." |
| Link used or expired | Confirm and undo pages | "Dieser Link ist abgelaufen oder wurde schon verwendet." with a way back to the portal. |
| Contrast warning | Darstellung | Warning box with the reason; "Trotzdem speichern" and "Andere Farbe wählen". |
| Backup older than 26 hours / failed mail | Status | The value turns red with a sentence saying what it means. |
| No account match | Sign-in | A generic refusal; no hint whether the name exists. |
## Interaction Primitives
- **Keyboard:** everything works with the keyboard; the focus outline is always visible; a "Zum Inhalt springen" link is the first tab stop. Escape closes the profile menu and dialogs.
- **Touch:** targets are at least 44 px; no hover-only controls.
- **Preferences per device:** theme (Hell, Dunkel, System; default System), language (default from the browser; switchable), and the German address form (default formal). They live in the profile menu from the avatar in the upper right and take effect without leaving the page. They are not stored on the server.
- **Confirmation links:** a link opens a page that names the change; only a button press applies it.
- **Banned:** infinite scroll, hover-only actions, dialogs stacked more than one deep, countdown timers visible to ordinary users, any action that runs on opening a link.
## Accessibility Floor
Behavioral. Visual contrast lives in `DESIGN.md`. Target: WCAG 2.1 AA.
- Full keyboard use and a skip link.
- Colour is never the only signal; banners carry an icon and a word.
- Form errors are linked to their fields and announced to screen readers.
- Headings are in order; each page has one main heading.
- Labels are visible, never only placeholders.
- Text can be enlarged to 200 percent without loss of content.
- The Admin accent colour is checked for contrast before it can be applied, with a warning.
## Responsive & Platform
Two layouts. Wide: left menu plus content. Narrow (phones): content only, with the menu behind a menu button; label/value rows stack; the two-column card grids become one column. The break is around 720 px. [ASSUMPTION: exact breakpoint decided at build.] Dark and light follow the system by default.
## Inspiration & Anti-patterns
Inspiration: the Microsoft 365 admin center's settings pages (calm, one topic per page, explanations beside controls). Anti-patterns: dense admin tables for ordinary users; long walls of help text; jargon from directory services; many accent colours.
## Key Flows
Journeys mirror the PRD (UJ-1 to UJ-4).
- **UJ-1. Lena changes her password before it leaks.** Lena signs in through authentik and lands on the dashboard. She opens Passwort. The page tells her the effect (other devices are signed out, she gets an e-mail) and lists the rules. She enters her current password and the new one twice and presses "Passwort ändern". *Climax:* the page says her password was changed. *Resolution:* she receives a confirmation e-mail. *Edge:* a rule is broken; the reason shows in plain words and nothing changes.
- **UJ-2. Tom sets a profile picture from his iPhone.** On Profilbild Tom picks a HEIC photo, crops it square and saves. *Climax:* the page shows his current picture with the chip "Wird aktualisiert" and the two-hour note. *Resolution:* when the sync has run, the chip disappears. *Edge:* a 14 MB file is refused with a reason before upload.
- **UJ-3. Ebbe announces a maintenance window.** In Ankündigungen verwalten Ebbe writes a title, sets importance to Warnung, writes the text in the editor, sets the maintenance window and an expiry, and schedules it for the next morning. The recipient summary shows who will be e-mailed. *Climax:* at the scheduled time the banner appears on every dashboard and the e-mail goes out. *Resolution:* after the move he withdraws it. *Edge:* he corrects the end time and chooses "Benutzer erneut benachrichtigen".
- **UJ-4. Marie sees an e-mail change she did not make.** Marie opens the undo link from the notice at her old address. The page names the change and shows one button. *Climax:* she presses "Änderung rückgängig machen" and her old address is back. *Resolution:* she is told to change her password, her sessions end, and the Admin is notified.
## Open items
- The password rules text depends on reading the domain policy from AD; this is a new requirement for the PRD (FR-5) and the architecture spine (AD-2 read allow-list).
- The WYSIWYG editor changes PRD FR-16 and AD-16; the editor library is chosen at build and must be built into the image.
- Severity is required with the default "Information" (PRD FR-17 listed it as optional).
- The Einstellungen page was not mocked; it is a labelled list of fields with an explanation beside each.
- The session-ended message and the exact phone breakpoint are assumptions.
@@ -0,0 +1,99 @@
<!doctype html><html lang="de"><meta charset="utf-8"><title>Darstellung (Admin) - contrast warning state</title><style>
/* Governing spec sections: EXPERIENCE.md Information Architecture, Component Patterns, State Patterns;
DESIGN.md Colors, Components. Mock only - the spines win on conflict. Offline: no JS, no network.
Brand blue is the Fluent 2 default (#0F6CBD) [ASSUMPTION, verify at scaffold]. German formal (Sie) is the default voice. */
:root{--brand:#0F6CBD;--on-brand:#fff}
*{box-sizing:border-box}
body{margin:0;padding:24px;font-family:"Segoe UI",system-ui,sans-serif;background:#e8e8e8;color:#242424}
h1.cap{font-size:14px;font-weight:600;margin:0 0 4px}
p.cap{font-size:12px;margin:0 0 16px;color:#555;max-width:900px}
.stage{display:flex;gap:32px;align-items:flex-start;flex-wrap:wrap}
.frame{border-radius:10px;overflow:hidden;box-shadow:0 4px 24px rgba(0,0,0,.25);border:1px solid #bbb}
.desk{width:980px}.phone{width:360px;border-radius:28px;border:8px solid #222}
.light{--bg:#fafafa;--card:#fff;--text:#242424;--sub:#616161;--line:#e0e0e0;--nav:#f5f5f5;--warn-bg:#fff9f5;--warn-line:#f7630c;--chip-bg:#fff4ce;--chip-text:#5c4400;--field:#fff;--fieldline:#8a8a8a}
.dark{--bg:#1f1f1f;--card:#292929;--text:#ffffff;--sub:#c8c8c8;--line:#3d3d3d;--nav:#242424;--warn-bg:#3a2a1f;--warn-line:#f7630c;--chip-bg:#4a3b00;--chip-text:#ffe9a8;--field:#333;--fieldline:#9a9a9a;--brand:#479ef5;--on-brand:#0a0a0a}
.app{background:var(--bg);color:var(--text);display:flex;flex-direction:column;min-height:560px}
.top{height:48px;background:var(--brand);color:var(--on-brand);display:flex;align-items:center;justify-content:space-between;padding:0 16px;font-weight:600}
.dark .top{background:#0f2a43;color:#fff}
.av{width:32px;height:32px;border-radius:50%;background:#fff;color:var(--brand);display:grid;place-items:center;font-size:12px;font-weight:700}
.dark .av{background:#479ef5;color:#0a0a0a}
.body{display:flex;flex:1}
.nav{width:220px;background:var(--nav);border-right:1px solid var(--line);padding:12px 8px}
.nav a{display:flex;align-items:center;gap:10px;padding:9px 12px;border-radius:4px;color:var(--text);font-size:14px;margin-bottom:2px}
.nav a.on{background:var(--card);font-weight:600;box-shadow:inset 3px 0 0 var(--brand)}
.nav i{width:14px;height:14px;border-radius:3px;background:var(--sub);opacity:.6;display:inline-block}
.main{flex:1;min-width:0;padding:24px 28px}
.phone .main{padding:16px}
.phone .row{flex-direction:column;gap:2px}
.phone .row span:first-child{width:auto}
.row span:last-child{min-width:0;overflow-wrap:anywhere}
.phone .card{padding:14px}
h2{font-size:24px;font-weight:600;margin:0 0 4px}.phone h2{font-size:20px}
.lead{font-size:14px;color:var(--sub);margin:0 0 18px;max-width:640px}
.card{background:var(--card);border:1px solid var(--line);border-radius:8px;padding:16px 18px;margin-bottom:14px}
.card h3{font-size:16px;margin:0 0 8px}
.banner{border:1px solid var(--warn-line);background:var(--warn-bg);border-left-width:5px;border-radius:6px;padding:14px 16px;margin-bottom:14px;font-size:14px}
.banner b{display:block;margin-bottom:4px}
.grid{display:grid;grid-template-columns:1fr 1fr;gap:14px}.phone .grid{grid-template-columns:1fr}
.row{display:flex;gap:10px;font-size:14px;padding:5px 0;border-bottom:1px solid var(--line)}
.row span:first-child{width:130px;color:var(--sub);flex:none}
.pic{width:72px;height:72px;border-radius:50%;background:var(--brand);color:var(--on-brand);display:grid;place-items:center;font-size:26px;font-weight:600;flex:none}
.btn{display:inline-block;background:var(--brand);color:var(--on-brand);border-radius:4px;padding:8px 16px;font-size:14px;font-weight:600;margin:0 8px 6px 0}
.btn.sec{background:transparent;color:var(--text);border:1px solid var(--fieldline)}
.field{margin-bottom:14px}.field label{display:block;font-size:14px;font-weight:600;margin-bottom:4px}
.field .in{height:34px;border:1px solid var(--fieldline);border-bottom-width:2px;border-radius:4px;background:var(--field);padding:6px 10px;font-size:14px;color:var(--sub)}
.hint{font-size:12px;color:var(--sub);margin-top:4px}
.chip{display:inline-block;background:var(--chip-bg);color:var(--chip-text);border-radius:10px;padding:2px 10px;font-size:12px;font-weight:600}
ul.rules{margin:6px 0 0;padding-left:20px;font-size:13px;color:var(--sub)}
.small{font-size:13px;color:var(--sub)}
.menu{position:absolute;right:16px;top:44px;background:var(--card);color:var(--text);border:1px solid var(--line);border-radius:8px;padding:12px 14px;font-size:13px;width:240px;box-shadow:0 6px 20px rgba(0,0,0,.25)}
.menu div{padding:4px 0}
.rel{position:relative}
.burger{display:inline-block;width:18px;height:2px;background:currentColor;box-shadow:0 6px 0 currentColor,0 -6px 0 currentColor}
table{width:100%;border-collapse:collapse;font-size:13px}
th{text-align:left;color:var(--sub);font-weight:600;padding:6px 8px;border-bottom:1px solid var(--line)}
td{padding:7px 8px;border-bottom:1px solid var(--line);vertical-align:top}
.ok{color:#107c10}.dark .ok{color:#6ccb5f}
.bad{color:#b10e1c;font-weight:600}.dark .bad{color:#ff99a4}
.ta{border:1px solid var(--fieldline);border-bottom-width:2px;border-radius:4px;background:var(--field);padding:8px 10px;font-size:13px;min-height:110px;white-space:pre-wrap;font-family:Consolas,monospace;color:var(--text)}
.two{display:grid;grid-template-columns:1fr 1fr;gap:14px}.phone .two{grid-template-columns:1fr}
.radio{display:inline-block;margin-right:14px;font-size:14px}
.radio:before{content:"";display:inline-block;width:12px;height:12px;border-radius:50%;border:2px solid var(--fieldline);margin-right:6px;vertical-align:-2px}
.radio.sel:before{border-color:var(--brand);background:radial-gradient(var(--brand) 40%,transparent 45%)}
.swatch{display:inline-block;width:40px;height:28px;border-radius:4px;border:1px solid var(--fieldline);vertical-align:middle;margin-right:8px}
.warnbox{border:1px solid var(--warn-line);background:var(--warn-bg);border-left-width:5px;border-radius:6px;padding:10px 14px;font-size:13px;margin-top:10px}
.filters span{display:inline-block;border:1px solid var(--fieldline);border-radius:14px;padding:2px 10px;font-size:12px;margin:0 6px 6px 0}
.filters span.sel{background:var(--brand);color:var(--on-brand);border-color:var(--brand)}
.navh{font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:var(--sub);padding:12px 12px 4px}
.tb{display:flex;gap:2px;border:1px solid var(--fieldline);border-bottom:0;border-radius:4px 4px 0 0;background:var(--nav);padding:4px}
.tb span{padding:3px 10px;font-size:13px;border-radius:3px}
.wys{border:1px solid var(--fieldline);border-bottom-width:2px;border-radius:0 0 4px 4px;background:var(--field);padding:10px 12px;font-size:14px;min-height:120px}
.preview{border:1px dashed var(--fieldline);border-radius:6px;padding:10px 12px;font-size:13px}
</style><body><h1 class="cap">Darstellung (Admin) - contrast warning state</h1><p class="cap">Accent colour with the contrast check for both themes and the warning; saving stays possible. Background upload. Illustrates: FR-22 and the check-and-warn decision. Colour values are computed.</p><div class="stage"><div class="frame desk light"><div class="app rel"><div class="top"><span>ping-mee Portal</span><span class="av">EB</span></div><div class="body"><div class="nav"><a class=""><i></i>Startseite</a><a class=""><i></i>Profil</a><a class=""><i></i>Profilbild</a><a class=""><i></i>Passwort</a><a class=""><i></i>Ankündigungen</a><a class=""><i></i>Benachrichtigungen</a><div class="navh">Veröffentlichen</div><a class=""><i></i>Ankündigungen verwalten</a><div class="navh">Administration</div><a class="on"><i></i>Darstellung</a><a class=""><i></i>Einstellungen</a><a class=""><i></i>Protokoll</a><a class=""><i></i>Status</a></div><div class="main">
<h2>Darstellung</h2>
<p class="lead">Farbe und Hintergrund gelten für alle Benutzer des Portals.</p>
<div class="card" style="max-width:640px"><h3>Akzentfarbe</h3>
<div class="field"><label>Farbe</label><span class="swatch" style="background:#FFD60A"></span><span class="in" style="display:inline-block;width:120px">#FFD60A</span> <span class="small">&nbsp;Standard: #0F6CBD</span></div>
<table><tr><th>Anzeige</th><th>Text auf der Farbe</th><th>Kontrast</th><th>Ergebnis</th></tr>
<tr><td>Hell</td><td>Weiß</td><td>1.4 : 1</td><td class="bad">zu niedrig (Minimum 4,5 : 1)</td></tr>
<tr><td>Dunkel</td><td>Fast schwarz</td><td>14.0 : 1</td><td class="ok">ausreichend</td></tr></table>
<div class="warnbox"><b>Warnung:</b> Im hellen Design ist weißer Text auf dieser Farbe schwer lesbar. Sie können sie trotzdem speichern, aber manche Benutzer erkennen Schaltflächen dann schlechter.</div>
<p style="margin:14px 0 0"><span class="btn">Trotzdem speichern</span><span class="btn sec">Andere Farbe wählen</span></p></div>
<div class="card" style="max-width:640px"><h3>Hintergrundbild</h3>
<p class="small" style="margin:0 0 8px">Das Bild erscheint nur für angemeldete Benutzer. Es wird beim Hochladen neu kodiert; bis 10 MB.</p>
<span class="btn sec">Bild hochladen</span><span class="btn sec">Kein Bild</span></div>
</div></div></div></div><div class="frame phone dark"><div class="app rel"><div class="top"><span class="burger"></span><span>ping-mee Portal</span><span class="av">EB</span></div><div class="body"><div class="main">
<h2>Darstellung</h2>
<p class="lead">Farbe und Hintergrund gelten für alle Benutzer des Portals.</p>
<div class="card" style="max-width:640px"><h3>Akzentfarbe</h3>
<div class="field"><label>Farbe</label><span class="swatch" style="background:#FFD60A"></span><span class="in" style="display:inline-block;width:120px">#FFD60A</span> <span class="small">&nbsp;Standard: #0F6CBD</span></div>
<table><tr><th>Anzeige</th><th>Text auf der Farbe</th><th>Kontrast</th><th>Ergebnis</th></tr>
<tr><td>Hell</td><td>Weiß</td><td>1.4 : 1</td><td class="bad">zu niedrig (Minimum 4,5 : 1)</td></tr>
<tr><td>Dunkel</td><td>Fast schwarz</td><td>14.0 : 1</td><td class="ok">ausreichend</td></tr></table>
<div class="warnbox"><b>Warnung:</b> Im hellen Design ist weißer Text auf dieser Farbe schwer lesbar. Sie können sie trotzdem speichern, aber manche Benutzer erkennen Schaltflächen dann schlechter.</div>
<p style="margin:14px 0 0"><span class="btn">Trotzdem speichern</span><span class="btn sec">Andere Farbe wählen</span></p></div>
<div class="card" style="max-width:640px"><h3>Hintergrundbild</h3>
<p class="small" style="margin:0 0 8px">Das Bild erscheint nur für angemeldete Benutzer. Es wird beim Hochladen neu kodiert; bis 10 MB.</p>
<span class="btn sec">Bild hochladen</span><span class="btn sec">Kein Bild</span></div>
</div></div></div></div></div></body></html>
@@ -0,0 +1,91 @@
<!doctype html><html lang="de"><meta charset="utf-8"><title>Protokoll (Admin)</title><style>
/* Governing spec sections: EXPERIENCE.md Information Architecture, Component Patterns, State Patterns;
DESIGN.md Colors, Components. Mock only - the spines win on conflict. Offline: no JS, no network.
Brand blue is the Fluent 2 default (#0F6CBD) [ASSUMPTION, verify at scaffold]. German formal (Sie) is the default voice. */
:root{--brand:#0F6CBD;--on-brand:#fff}
*{box-sizing:border-box}
body{margin:0;padding:24px;font-family:"Segoe UI",system-ui,sans-serif;background:#e8e8e8;color:#242424}
h1.cap{font-size:14px;font-weight:600;margin:0 0 4px}
p.cap{font-size:12px;margin:0 0 16px;color:#555;max-width:900px}
.stage{display:flex;gap:32px;align-items:flex-start;flex-wrap:wrap}
.frame{border-radius:10px;overflow:hidden;box-shadow:0 4px 24px rgba(0,0,0,.25);border:1px solid #bbb}
.desk{width:980px}.phone{width:360px;border-radius:28px;border:8px solid #222}
.light{--bg:#fafafa;--card:#fff;--text:#242424;--sub:#616161;--line:#e0e0e0;--nav:#f5f5f5;--warn-bg:#fff9f5;--warn-line:#f7630c;--chip-bg:#fff4ce;--chip-text:#5c4400;--field:#fff;--fieldline:#8a8a8a}
.dark{--bg:#1f1f1f;--card:#292929;--text:#ffffff;--sub:#c8c8c8;--line:#3d3d3d;--nav:#242424;--warn-bg:#3a2a1f;--warn-line:#f7630c;--chip-bg:#4a3b00;--chip-text:#ffe9a8;--field:#333;--fieldline:#9a9a9a;--brand:#479ef5;--on-brand:#0a0a0a}
.app{background:var(--bg);color:var(--text);display:flex;flex-direction:column;min-height:560px}
.top{height:48px;background:var(--brand);color:var(--on-brand);display:flex;align-items:center;justify-content:space-between;padding:0 16px;font-weight:600}
.dark .top{background:#0f2a43;color:#fff}
.av{width:32px;height:32px;border-radius:50%;background:#fff;color:var(--brand);display:grid;place-items:center;font-size:12px;font-weight:700}
.dark .av{background:#479ef5;color:#0a0a0a}
.body{display:flex;flex:1}
.nav{width:220px;background:var(--nav);border-right:1px solid var(--line);padding:12px 8px}
.nav a{display:flex;align-items:center;gap:10px;padding:9px 12px;border-radius:4px;color:var(--text);font-size:14px;margin-bottom:2px}
.nav a.on{background:var(--card);font-weight:600;box-shadow:inset 3px 0 0 var(--brand)}
.nav i{width:14px;height:14px;border-radius:3px;background:var(--sub);opacity:.6;display:inline-block}
.main{flex:1;min-width:0;padding:24px 28px}
.phone .main{padding:16px}
.phone .row{flex-direction:column;gap:2px}
.phone .row span:first-child{width:auto}
.row span:last-child{min-width:0;overflow-wrap:anywhere}
.phone .card{padding:14px}
h2{font-size:24px;font-weight:600;margin:0 0 4px}.phone h2{font-size:20px}
.lead{font-size:14px;color:var(--sub);margin:0 0 18px;max-width:640px}
.card{background:var(--card);border:1px solid var(--line);border-radius:8px;padding:16px 18px;margin-bottom:14px}
.card h3{font-size:16px;margin:0 0 8px}
.banner{border:1px solid var(--warn-line);background:var(--warn-bg);border-left-width:5px;border-radius:6px;padding:14px 16px;margin-bottom:14px;font-size:14px}
.banner b{display:block;margin-bottom:4px}
.grid{display:grid;grid-template-columns:1fr 1fr;gap:14px}.phone .grid{grid-template-columns:1fr}
.row{display:flex;gap:10px;font-size:14px;padding:5px 0;border-bottom:1px solid var(--line)}
.row span:first-child{width:130px;color:var(--sub);flex:none}
.pic{width:72px;height:72px;border-radius:50%;background:var(--brand);color:var(--on-brand);display:grid;place-items:center;font-size:26px;font-weight:600;flex:none}
.btn{display:inline-block;background:var(--brand);color:var(--on-brand);border-radius:4px;padding:8px 16px;font-size:14px;font-weight:600;margin:0 8px 6px 0}
.btn.sec{background:transparent;color:var(--text);border:1px solid var(--fieldline)}
.field{margin-bottom:14px}.field label{display:block;font-size:14px;font-weight:600;margin-bottom:4px}
.field .in{height:34px;border:1px solid var(--fieldline);border-bottom-width:2px;border-radius:4px;background:var(--field);padding:6px 10px;font-size:14px;color:var(--sub)}
.hint{font-size:12px;color:var(--sub);margin-top:4px}
.chip{display:inline-block;background:var(--chip-bg);color:var(--chip-text);border-radius:10px;padding:2px 10px;font-size:12px;font-weight:600}
ul.rules{margin:6px 0 0;padding-left:20px;font-size:13px;color:var(--sub)}
.small{font-size:13px;color:var(--sub)}
.menu{position:absolute;right:16px;top:44px;background:var(--card);color:var(--text);border:1px solid var(--line);border-radius:8px;padding:12px 14px;font-size:13px;width:240px;box-shadow:0 6px 20px rgba(0,0,0,.25)}
.menu div{padding:4px 0}
.rel{position:relative}
.burger{display:inline-block;width:18px;height:2px;background:currentColor;box-shadow:0 6px 0 currentColor,0 -6px 0 currentColor}
table{width:100%;border-collapse:collapse;font-size:13px}
th{text-align:left;color:var(--sub);font-weight:600;padding:6px 8px;border-bottom:1px solid var(--line)}
td{padding:7px 8px;border-bottom:1px solid var(--line);vertical-align:top}
.ok{color:#107c10}.dark .ok{color:#6ccb5f}
.bad{color:#b10e1c;font-weight:600}.dark .bad{color:#ff99a4}
.ta{border:1px solid var(--fieldline);border-bottom-width:2px;border-radius:4px;background:var(--field);padding:8px 10px;font-size:13px;min-height:110px;white-space:pre-wrap;font-family:Consolas,monospace;color:var(--text)}
.two{display:grid;grid-template-columns:1fr 1fr;gap:14px}.phone .two{grid-template-columns:1fr}
.radio{display:inline-block;margin-right:14px;font-size:14px}
.radio:before{content:"";display:inline-block;width:12px;height:12px;border-radius:50%;border:2px solid var(--fieldline);margin-right:6px;vertical-align:-2px}
.radio.sel:before{border-color:var(--brand);background:radial-gradient(var(--brand) 40%,transparent 45%)}
.swatch{display:inline-block;width:40px;height:28px;border-radius:4px;border:1px solid var(--fieldline);vertical-align:middle;margin-right:8px}
.warnbox{border:1px solid var(--warn-line);background:var(--warn-bg);border-left-width:5px;border-radius:6px;padding:10px 14px;font-size:13px;margin-top:10px}
.filters span{display:inline-block;border:1px solid var(--fieldline);border-radius:14px;padding:2px 10px;font-size:12px;margin:0 6px 6px 0}
.filters span.sel{background:var(--brand);color:var(--on-brand);border-color:var(--brand)}
.navh{font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:var(--sub);padding:12px 12px 4px}
.tb{display:flex;gap:2px;border:1px solid var(--fieldline);border-bottom:0;border-radius:4px 4px 0 0;background:var(--nav);padding:4px}
.tb span{padding:3px 10px;font-size:13px;border-radius:3px}
.wys{border:1px solid var(--fieldline);border-bottom-width:2px;border-radius:0 0 4px 4px;background:var(--field);padding:10px 12px;font-size:14px;min-height:120px}
.preview{border:1px dashed var(--fieldline);border-radius:6px;padding:10px 12px;font-size:13px}
</style><body><h1 class="cap">Protokoll (Admin)</h1><p class="cap">Audit log with filter chips, 12-month note, IP column. Illustrates: FR-24 and FR-28. Sample data.</p><div class="stage"><div class="frame desk light"><div class="app rel"><div class="top"><span>ping-mee Portal</span><span class="av">EB</span></div><div class="body"><div class="nav"><a class=""><i></i>Startseite</a><a class=""><i></i>Profil</a><a class=""><i></i>Profilbild</a><a class=""><i></i>Passwort</a><a class=""><i></i>Ankündigungen</a><a class=""><i></i>Benachrichtigungen</a><div class="navh">Veröffentlichen</div><a class=""><i></i>Ankündigungen verwalten</a><div class="navh">Administration</div><a class=""><i></i>Darstellung</a><a class=""><i></i>Einstellungen</a><a class="on"><i></i>Protokoll</a><a class=""><i></i>Status</a></div><div class="main">
<h2>Protokoll</h2><p class="lead">Wer hat wann was geändert? Einträge werden 12 Monate aufbewahrt. Passwörter und Bildinhalte werden nie gespeichert.</p>
<div class="filters"><span class="sel">Alle</span><span>Passwort</span><span>Profil</span><span>E-Mail-Änderung</span><span>Profilbild</span><span>Ankündigungen</span><span>Einstellungen</span><span>Gedrosselt</span></div>
<div class="card"><table><tr><th>Zeit</th><th>Benutzer</th><th>Aktion</th><th>IP-Adresse</th></tr>
<tr><td>15.10. 09:41</td><td>lmeier</td><td>Passwort geändert</td><td>203.0.113.24</td></tr>
<tr><td>15.10. 09:40</td><td>lmeier</td><td>Falsches aktuelles Passwort (1 von 5)</td><td>203.0.113.24</td></tr>
<tr><td>15.10. 08:12</td><td>tkoch</td><td>Profilbild hochgeladen</td><td>198.51.100.7</td></tr>
<tr><td>14.10. 20:03</td><td>msommer</td><td>E-Mail-Änderung rückgängig gemacht</td><td>198.51.100.33</td></tr>
<tr><td>14.10. 17:30</td><td>ebass</td><td>Ankündigung veröffentlicht</td><td>192.0.2.10</td></tr></table></div>
</div></div></div></div><div class="frame phone dark"><div class="app rel"><div class="top"><span class="burger"></span><span>ping-mee Portal</span><span class="av">EB</span></div><div class="body"><div class="main">
<h2>Protokoll</h2><p class="lead">Wer hat wann was geändert? Einträge werden 12 Monate aufbewahrt. Passwörter und Bildinhalte werden nie gespeichert.</p>
<div class="filters"><span class="sel">Alle</span><span>Passwort</span><span>Profil</span><span>E-Mail-Änderung</span><span>Profilbild</span><span>Ankündigungen</span><span>Einstellungen</span><span>Gedrosselt</span></div>
<div class="card"><table><tr><th>Zeit</th><th>Benutzer</th><th>Aktion</th><th>IP-Adresse</th></tr>
<tr><td>15.10. 09:41</td><td>lmeier</td><td>Passwort geändert</td><td>203.0.113.24</td></tr>
<tr><td>15.10. 09:40</td><td>lmeier</td><td>Falsches aktuelles Passwort (1 von 5)</td><td>203.0.113.24</td></tr>
<tr><td>15.10. 08:12</td><td>tkoch</td><td>Profilbild hochgeladen</td><td>198.51.100.7</td></tr>
<tr><td>14.10. 20:03</td><td>msommer</td><td>E-Mail-Änderung rückgängig gemacht</td><td>198.51.100.33</td></tr>
<tr><td>14.10. 17:30</td><td>ebass</td><td>Ankündigung veröffentlicht</td><td>192.0.2.10</td></tr></table></div>
</div></div></div></div></div></body></html>
@@ -0,0 +1,103 @@
<!doctype html><html lang="de"><meta charset="utf-8"><title>Status (Admin)</title><style>
/* Governing spec sections: EXPERIENCE.md Information Architecture, Component Patterns, State Patterns;
DESIGN.md Colors, Components. Mock only - the spines win on conflict. Offline: no JS, no network.
Brand blue is the Fluent 2 default (#0F6CBD) [ASSUMPTION, verify at scaffold]. German formal (Sie) is the default voice. */
:root{--brand:#0F6CBD;--on-brand:#fff}
*{box-sizing:border-box}
body{margin:0;padding:24px;font-family:"Segoe UI",system-ui,sans-serif;background:#e8e8e8;color:#242424}
h1.cap{font-size:14px;font-weight:600;margin:0 0 4px}
p.cap{font-size:12px;margin:0 0 16px;color:#555;max-width:900px}
.stage{display:flex;gap:32px;align-items:flex-start;flex-wrap:wrap}
.frame{border-radius:10px;overflow:hidden;box-shadow:0 4px 24px rgba(0,0,0,.25);border:1px solid #bbb}
.desk{width:980px}.phone{width:360px;border-radius:28px;border:8px solid #222}
.light{--bg:#fafafa;--card:#fff;--text:#242424;--sub:#616161;--line:#e0e0e0;--nav:#f5f5f5;--warn-bg:#fff9f5;--warn-line:#f7630c;--chip-bg:#fff4ce;--chip-text:#5c4400;--field:#fff;--fieldline:#8a8a8a}
.dark{--bg:#1f1f1f;--card:#292929;--text:#ffffff;--sub:#c8c8c8;--line:#3d3d3d;--nav:#242424;--warn-bg:#3a2a1f;--warn-line:#f7630c;--chip-bg:#4a3b00;--chip-text:#ffe9a8;--field:#333;--fieldline:#9a9a9a;--brand:#479ef5;--on-brand:#0a0a0a}
.app{background:var(--bg);color:var(--text);display:flex;flex-direction:column;min-height:560px}
.top{height:48px;background:var(--brand);color:var(--on-brand);display:flex;align-items:center;justify-content:space-between;padding:0 16px;font-weight:600}
.dark .top{background:#0f2a43;color:#fff}
.av{width:32px;height:32px;border-radius:50%;background:#fff;color:var(--brand);display:grid;place-items:center;font-size:12px;font-weight:700}
.dark .av{background:#479ef5;color:#0a0a0a}
.body{display:flex;flex:1}
.nav{width:220px;background:var(--nav);border-right:1px solid var(--line);padding:12px 8px}
.nav a{display:flex;align-items:center;gap:10px;padding:9px 12px;border-radius:4px;color:var(--text);font-size:14px;margin-bottom:2px}
.nav a.on{background:var(--card);font-weight:600;box-shadow:inset 3px 0 0 var(--brand)}
.nav i{width:14px;height:14px;border-radius:3px;background:var(--sub);opacity:.6;display:inline-block}
.main{flex:1;min-width:0;padding:24px 28px}
.phone .main{padding:16px}
.phone .row{flex-direction:column;gap:2px}
.phone .row span:first-child{width:auto}
.row span:last-child{min-width:0;overflow-wrap:anywhere}
.phone .card{padding:14px}
h2{font-size:24px;font-weight:600;margin:0 0 4px}.phone h2{font-size:20px}
.lead{font-size:14px;color:var(--sub);margin:0 0 18px;max-width:640px}
.card{background:var(--card);border:1px solid var(--line);border-radius:8px;padding:16px 18px;margin-bottom:14px}
.card h3{font-size:16px;margin:0 0 8px}
.banner{border:1px solid var(--warn-line);background:var(--warn-bg);border-left-width:5px;border-radius:6px;padding:14px 16px;margin-bottom:14px;font-size:14px}
.banner b{display:block;margin-bottom:4px}
.grid{display:grid;grid-template-columns:1fr 1fr;gap:14px}.phone .grid{grid-template-columns:1fr}
.row{display:flex;gap:10px;font-size:14px;padding:5px 0;border-bottom:1px solid var(--line)}
.row span:first-child{width:130px;color:var(--sub);flex:none}
.pic{width:72px;height:72px;border-radius:50%;background:var(--brand);color:var(--on-brand);display:grid;place-items:center;font-size:26px;font-weight:600;flex:none}
.btn{display:inline-block;background:var(--brand);color:var(--on-brand);border-radius:4px;padding:8px 16px;font-size:14px;font-weight:600;margin:0 8px 6px 0}
.btn.sec{background:transparent;color:var(--text);border:1px solid var(--fieldline)}
.field{margin-bottom:14px}.field label{display:block;font-size:14px;font-weight:600;margin-bottom:4px}
.field .in{height:34px;border:1px solid var(--fieldline);border-bottom-width:2px;border-radius:4px;background:var(--field);padding:6px 10px;font-size:14px;color:var(--sub)}
.hint{font-size:12px;color:var(--sub);margin-top:4px}
.chip{display:inline-block;background:var(--chip-bg);color:var(--chip-text);border-radius:10px;padding:2px 10px;font-size:12px;font-weight:600}
ul.rules{margin:6px 0 0;padding-left:20px;font-size:13px;color:var(--sub)}
.small{font-size:13px;color:var(--sub)}
.menu{position:absolute;right:16px;top:44px;background:var(--card);color:var(--text);border:1px solid var(--line);border-radius:8px;padding:12px 14px;font-size:13px;width:240px;box-shadow:0 6px 20px rgba(0,0,0,.25)}
.menu div{padding:4px 0}
.rel{position:relative}
.burger{display:inline-block;width:18px;height:2px;background:currentColor;box-shadow:0 6px 0 currentColor,0 -6px 0 currentColor}
table{width:100%;border-collapse:collapse;font-size:13px}
th{text-align:left;color:var(--sub);font-weight:600;padding:6px 8px;border-bottom:1px solid var(--line)}
td{padding:7px 8px;border-bottom:1px solid var(--line);vertical-align:top}
.ok{color:#107c10}.dark .ok{color:#6ccb5f}
.bad{color:#b10e1c;font-weight:600}.dark .bad{color:#ff99a4}
.ta{border:1px solid var(--fieldline);border-bottom-width:2px;border-radius:4px;background:var(--field);padding:8px 10px;font-size:13px;min-height:110px;white-space:pre-wrap;font-family:Consolas,monospace;color:var(--text)}
.two{display:grid;grid-template-columns:1fr 1fr;gap:14px}.phone .two{grid-template-columns:1fr}
.radio{display:inline-block;margin-right:14px;font-size:14px}
.radio:before{content:"";display:inline-block;width:12px;height:12px;border-radius:50%;border:2px solid var(--fieldline);margin-right:6px;vertical-align:-2px}
.radio.sel:before{border-color:var(--brand);background:radial-gradient(var(--brand) 40%,transparent 45%)}
.swatch{display:inline-block;width:40px;height:28px;border-radius:4px;border:1px solid var(--fieldline);vertical-align:middle;margin-right:8px}
.warnbox{border:1px solid var(--warn-line);background:var(--warn-bg);border-left-width:5px;border-radius:6px;padding:10px 14px;font-size:13px;margin-top:10px}
.filters span{display:inline-block;border:1px solid var(--fieldline);border-radius:14px;padding:2px 10px;font-size:12px;margin:0 6px 6px 0}
.filters span.sel{background:var(--brand);color:var(--on-brand);border-color:var(--brand)}
.navh{font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:var(--sub);padding:12px 12px 4px}
.tb{display:flex;gap:2px;border:1px solid var(--fieldline);border-bottom:0;border-radius:4px 4px 0 0;background:var(--nav);padding:4px}
.tb span{padding:3px 10px;font-size:13px;border-radius:3px}
.wys{border:1px solid var(--fieldline);border-bottom-width:2px;border-radius:0 0 4px 4px;background:var(--field);padding:10px 12px;font-size:14px;min-height:120px}
.preview{border:1px dashed var(--fieldline);border-radius:6px;padding:10px 12px;font-size:13px}
</style><body><h1 class="cap">Status (Admin)</h1><p class="cap">Backup age, failed e-mails and job runs, all in one place because nothing else alerts the Admin. Illustrates: AD-12, AD-14 status surfaces.</p><div class="stage"><div class="frame desk light"><div class="app rel"><div class="top"><span>ping-mee Portal</span><span class="av">EB</span></div><div class="body"><div class="nav"><a class=""><i></i>Startseite</a><a class=""><i></i>Profil</a><a class=""><i></i>Profilbild</a><a class=""><i></i>Passwort</a><a class=""><i></i>Ankündigungen</a><a class=""><i></i>Benachrichtigungen</a><div class="navh">Veröffentlichen</div><a class=""><i></i>Ankündigungen verwalten</a><div class="navh">Administration</div><a class=""><i></i>Darstellung</a><a class=""><i></i>Einstellungen</a><a class=""><i></i>Protokoll</a><a class="on"><i></i>Status</a></div><div class="main">
<h2>Status</h2><p class="lead">Hier sehen Sie, ob die Hintergrundaufgaben laufen. Probleme bei Datensicherung und E-Mails erscheinen nur hier.</p>
<div class="two">
<div class="card"><h3>Datensicherung</h3><p style="margin:0"><b class="ok">Letzte erfolgreiche Sicherung: vor 3 Stunden</b></p><p class="small">Ihre Veeam-Kopie sichert diesen Ordner. Ist die Sicherung älter als 26 Stunden, wird sie hier rot.</p></div>
<div class="card"><h3>E-Mail-Versand</h3><p style="margin:0"><b class="bad">2 E-Mails fehlgeschlagen</b></p><p class="small">Der Versand wird automatisch wiederholt. Nach 5 Versuchen bleibt die E-Mail hier stehen.</p></div></div>
<div class="card"><h3>Fehlgeschlagene E-Mails</h3>
<table><tr><th>Empfänger</th><th>Betreff</th><th>Versuche</th><th>Grund</th></tr>
<tr><td>tom.k@example.com</td><td>Geplante Wartung am Samstag</td><td>3</td><td>Postfach voll</td></tr>
<tr><td>marie.s@example.com</td><td>Geplante Wartung am Samstag</td><td>2</td><td>Server nicht erreichbar</td></tr></table></div>
<div class="card"><h3>Hintergrundaufgaben</h3>
<table><tr><th>Aufgabe</th><th>Letzter Lauf</th><th>Ergebnis</th></tr>
<tr><td>Geplante Ankündigungen</td><td>vor 20 Sekunden</td><td class="ok">in Ordnung</td></tr>
<tr><td>E-Mail-Versand</td><td>vor 20 Sekunden</td><td class="ok">in Ordnung</td></tr>
<tr><td>Abgleich mit dem Konto-Verzeichnis</td><td>heute 03:00 Uhr</td><td class="ok">in Ordnung</td></tr>
<tr><td>Protokoll aufräumen</td><td>heute 03:05 Uhr</td><td class="ok">in Ordnung</td></tr></table></div>
</div></div></div></div><div class="frame phone dark"><div class="app rel"><div class="top"><span class="burger"></span><span>ping-mee Portal</span><span class="av">EB</span></div><div class="body"><div class="main">
<h2>Status</h2><p class="lead">Hier sehen Sie, ob die Hintergrundaufgaben laufen. Probleme bei Datensicherung und E-Mails erscheinen nur hier.</p>
<div class="two">
<div class="card"><h3>Datensicherung</h3><p style="margin:0"><b class="ok">Letzte erfolgreiche Sicherung: vor 3 Stunden</b></p><p class="small">Ihre Veeam-Kopie sichert diesen Ordner. Ist die Sicherung älter als 26 Stunden, wird sie hier rot.</p></div>
<div class="card"><h3>E-Mail-Versand</h3><p style="margin:0"><b class="bad">2 E-Mails fehlgeschlagen</b></p><p class="small">Der Versand wird automatisch wiederholt. Nach 5 Versuchen bleibt die E-Mail hier stehen.</p></div></div>
<div class="card"><h3>Fehlgeschlagene E-Mails</h3>
<table><tr><th>Empfänger</th><th>Betreff</th><th>Versuche</th><th>Grund</th></tr>
<tr><td>tom.k@example.com</td><td>Geplante Wartung am Samstag</td><td>3</td><td>Postfach voll</td></tr>
<tr><td>marie.s@example.com</td><td>Geplante Wartung am Samstag</td><td>2</td><td>Server nicht erreichbar</td></tr></table></div>
<div class="card"><h3>Hintergrundaufgaben</h3>
<table><tr><th>Aufgabe</th><th>Letzter Lauf</th><th>Ergebnis</th></tr>
<tr><td>Geplante Ankündigungen</td><td>vor 20 Sekunden</td><td class="ok">in Ordnung</td></tr>
<tr><td>E-Mail-Versand</td><td>vor 20 Sekunden</td><td class="ok">in Ordnung</td></tr>
<tr><td>Abgleich mit dem Konto-Verzeichnis</td><td>heute 03:00 Uhr</td><td class="ok">in Ordnung</td></tr>
<tr><td>Protokoll aufräumen</td><td>heute 03:05 Uhr</td><td class="ok">in Ordnung</td></tr></table></div>
</div></div></div></div></div></body></html>
@@ -0,0 +1,109 @@
<!doctype html><html lang="de"><meta charset="utf-8"><title>Ankündigung verfassen (Publisher)</title><style>
/* Governing spec sections: EXPERIENCE.md Information Architecture, Component Patterns, State Patterns;
DESIGN.md Colors, Components. Mock only - the spines win on conflict. Offline: no JS, no network.
Brand blue is the Fluent 2 default (#0F6CBD) [ASSUMPTION, verify at scaffold]. German formal (Sie) is the default voice. */
:root{--brand:#0F6CBD;--on-brand:#fff}
*{box-sizing:border-box}
body{margin:0;padding:24px;font-family:"Segoe UI",system-ui,sans-serif;background:#e8e8e8;color:#242424}
h1.cap{font-size:14px;font-weight:600;margin:0 0 4px}
p.cap{font-size:12px;margin:0 0 16px;color:#555;max-width:900px}
.stage{display:flex;gap:32px;align-items:flex-start;flex-wrap:wrap}
.frame{border-radius:10px;overflow:hidden;box-shadow:0 4px 24px rgba(0,0,0,.25);border:1px solid #bbb}
.desk{width:980px}.phone{width:360px;border-radius:28px;border:8px solid #222}
.light{--bg:#fafafa;--card:#fff;--text:#242424;--sub:#616161;--line:#e0e0e0;--nav:#f5f5f5;--warn-bg:#fff9f5;--warn-line:#f7630c;--chip-bg:#fff4ce;--chip-text:#5c4400;--field:#fff;--fieldline:#8a8a8a}
.dark{--bg:#1f1f1f;--card:#292929;--text:#ffffff;--sub:#c8c8c8;--line:#3d3d3d;--nav:#242424;--warn-bg:#3a2a1f;--warn-line:#f7630c;--chip-bg:#4a3b00;--chip-text:#ffe9a8;--field:#333;--fieldline:#9a9a9a;--brand:#479ef5;--on-brand:#0a0a0a}
.app{background:var(--bg);color:var(--text);display:flex;flex-direction:column;min-height:560px}
.top{height:48px;background:var(--brand);color:var(--on-brand);display:flex;align-items:center;justify-content:space-between;padding:0 16px;font-weight:600}
.dark .top{background:#0f2a43;color:#fff}
.av{width:32px;height:32px;border-radius:50%;background:#fff;color:var(--brand);display:grid;place-items:center;font-size:12px;font-weight:700}
.dark .av{background:#479ef5;color:#0a0a0a}
.body{display:flex;flex:1}
.nav{width:220px;background:var(--nav);border-right:1px solid var(--line);padding:12px 8px}
.nav a{display:flex;align-items:center;gap:10px;padding:9px 12px;border-radius:4px;color:var(--text);font-size:14px;margin-bottom:2px}
.nav a.on{background:var(--card);font-weight:600;box-shadow:inset 3px 0 0 var(--brand)}
.nav i{width:14px;height:14px;border-radius:3px;background:var(--sub);opacity:.6;display:inline-block}
.main{flex:1;min-width:0;padding:24px 28px}
.phone .main{padding:16px}
.phone .row{flex-direction:column;gap:2px}
.phone .row span:first-child{width:auto}
.row span:last-child{min-width:0;overflow-wrap:anywhere}
.phone .card{padding:14px}
h2{font-size:24px;font-weight:600;margin:0 0 4px}.phone h2{font-size:20px}
.lead{font-size:14px;color:var(--sub);margin:0 0 18px;max-width:640px}
.card{background:var(--card);border:1px solid var(--line);border-radius:8px;padding:16px 18px;margin-bottom:14px}
.card h3{font-size:16px;margin:0 0 8px}
.banner{border:1px solid var(--warn-line);background:var(--warn-bg);border-left-width:5px;border-radius:6px;padding:14px 16px;margin-bottom:14px;font-size:14px}
.banner b{display:block;margin-bottom:4px}
.grid{display:grid;grid-template-columns:1fr 1fr;gap:14px}.phone .grid{grid-template-columns:1fr}
.row{display:flex;gap:10px;font-size:14px;padding:5px 0;border-bottom:1px solid var(--line)}
.row span:first-child{width:130px;color:var(--sub);flex:none}
.pic{width:72px;height:72px;border-radius:50%;background:var(--brand);color:var(--on-brand);display:grid;place-items:center;font-size:26px;font-weight:600;flex:none}
.btn{display:inline-block;background:var(--brand);color:var(--on-brand);border-radius:4px;padding:8px 16px;font-size:14px;font-weight:600;margin:0 8px 6px 0}
.btn.sec{background:transparent;color:var(--text);border:1px solid var(--fieldline)}
.field{margin-bottom:14px}.field label{display:block;font-size:14px;font-weight:600;margin-bottom:4px}
.field .in{height:34px;border:1px solid var(--fieldline);border-bottom-width:2px;border-radius:4px;background:var(--field);padding:6px 10px;font-size:14px;color:var(--sub)}
.hint{font-size:12px;color:var(--sub);margin-top:4px}
.chip{display:inline-block;background:var(--chip-bg);color:var(--chip-text);border-radius:10px;padding:2px 10px;font-size:12px;font-weight:600}
ul.rules{margin:6px 0 0;padding-left:20px;font-size:13px;color:var(--sub)}
.small{font-size:13px;color:var(--sub)}
.menu{position:absolute;right:16px;top:44px;background:var(--card);color:var(--text);border:1px solid var(--line);border-radius:8px;padding:12px 14px;font-size:13px;width:240px;box-shadow:0 6px 20px rgba(0,0,0,.25)}
.menu div{padding:4px 0}
.rel{position:relative}
.burger{display:inline-block;width:18px;height:2px;background:currentColor;box-shadow:0 6px 0 currentColor,0 -6px 0 currentColor}
table{width:100%;border-collapse:collapse;font-size:13px}
th{text-align:left;color:var(--sub);font-weight:600;padding:6px 8px;border-bottom:1px solid var(--line)}
td{padding:7px 8px;border-bottom:1px solid var(--line);vertical-align:top}
.ok{color:#107c10}.dark .ok{color:#6ccb5f}
.bad{color:#b10e1c;font-weight:600}.dark .bad{color:#ff99a4}
.ta{border:1px solid var(--fieldline);border-bottom-width:2px;border-radius:4px;background:var(--field);padding:8px 10px;font-size:13px;min-height:110px;white-space:pre-wrap;font-family:Consolas,monospace;color:var(--text)}
.two{display:grid;grid-template-columns:1fr 1fr;gap:14px}.phone .two{grid-template-columns:1fr}
.radio{display:inline-block;margin-right:14px;font-size:14px}
.radio:before{content:"";display:inline-block;width:12px;height:12px;border-radius:50%;border:2px solid var(--fieldline);margin-right:6px;vertical-align:-2px}
.radio.sel:before{border-color:var(--brand);background:radial-gradient(var(--brand) 40%,transparent 45%)}
.swatch{display:inline-block;width:40px;height:28px;border-radius:4px;border:1px solid var(--fieldline);vertical-align:middle;margin-right:8px}
.warnbox{border:1px solid var(--warn-line);background:var(--warn-bg);border-left-width:5px;border-radius:6px;padding:10px 14px;font-size:13px;margin-top:10px}
.filters span{display:inline-block;border:1px solid var(--fieldline);border-radius:14px;padding:2px 10px;font-size:12px;margin:0 6px 6px 0}
.filters span.sel{background:var(--brand);color:var(--on-brand);border-color:var(--brand)}
.navh{font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:var(--sub);padding:12px 12px 4px}
.tb{display:flex;gap:2px;border:1px solid var(--fieldline);border-bottom:0;border-radius:4px 4px 0 0;background:var(--nav);padding:4px}
.tb span{padding:3px 10px;font-size:13px;border-radius:3px}
.wys{border:1px solid var(--fieldline);border-bottom-width:2px;border-radius:0 0 4px 4px;background:var(--field);padding:10px 12px;font-size:14px;min-height:120px}
.preview{border:1px dashed var(--fieldline);border-radius:6px;padding:10px 12px;font-size:13px}
</style><body><h1 class="cap">Ankündigung verfassen (Publisher)</h1><p class="cap">Desktop light, phone dark. Title, importance (always set, default Information) and a WYSIWYG text editor; optional downtime window, expiry and schedule; recipient count with the reason for each gap. Illustrates: UJ-3, publisher flow. The left menu shows the extra entries for Publishers and Admins.</p><div class="stage"><div class="frame desk light"><div class="app rel"><div class="top"><span>ping-mee Portal</span><span class="av">EB</span></div><div class="body"><div class="nav"><a class=""><i></i>Startseite</a><a class=""><i></i>Profil</a><a class=""><i></i>Profilbild</a><a class=""><i></i>Passwort</a><a class=""><i></i>Ankündigungen</a><a class=""><i></i>Benachrichtigungen</a><div class="navh">Veröffentlichen</div><a class="on"><i></i>Ankündigungen verwalten</a><div class="navh">Administration</div><a class=""><i></i>Darstellung</a><a class=""><i></i>Einstellungen</a><a class=""><i></i>Protokoll</a><a class=""><i></i>Status</a></div><div class="main">
<h2>Ankündigung verfassen</h2>
<p class="lead">Ihre Ankündigung erscheint auf der Startseite und kann per E-Mail an alle angemeldeten Empfänger gehen.</p>
<div class="card"><div class="field"><label>Titel</label><div class="in" style="color:var(--text)">Geplante Wartung am Samstag</div></div>
<div class="field"><label>Wichtigkeit</label><span class="radio">Information</span><span class="radio sel">Warnung</span><span class="radio">Ausfall</span>
<div class="hint">Bestimmt die Farbe der Meldung auf der Startseite. Ohne Auswahl gilt „Information“.</div></div>
<div class="field"><label>Text</label>
<div class="tb"><span><b>F</b></span><span><i>K</i></span><span>Überschrift</span><span>Liste</span><span>Link</span></div>
<div class="wys">Jellyfin und die <b>Dateifreigabe</b> sind am Samstag von 08:00 bis 12:00 Uhr nicht erreichbar.<ul style="margin:8px 0 0;padding-left:20px"><li>Grund: Umzug auf neue Hardware</li><li>Alles andere läuft normal</li></ul></div>
<div class="hint">So sehen es Benutzer auf der Startseite und in der E-Mail.</div></div></div>
<div class="card"><h3>Weitere Angaben (optional)</h3>
<div class="two"><div class="field"><label>Wartungsfenster: Beginn</label><div class="in">18.10.2026 08:00</div></div><div class="field"><label>Wartungsfenster: Ende</label><div class="in">18.10.2026 12:00</div></div></div>
<div class="hint" style="margin:-6px 0 12px">Zeitzone: Europe/Berlin. Die Zeitzone steht auch in der E-Mail.</div>
<div class="two"><div class="field"><label>Ablaufdatum</label><div class="in">18.10.2026 13:00</div><div class="hint">Danach verschwindet die Ankündigung von der Startseite.</div></div>
<div class="field"><label>Veröffentlichen</label><span class="radio">Sofort</span><span class="radio sel">Geplant</span><div class="in" style="margin-top:6px">17.10.2026 08:00</div></div></div></div>
<div class="card"><h3>E-Mail</h3><div class="radio sel">Benutzer per E-Mail benachrichtigen</div>
<p class="small" style="margin:6px 0 0">Empfänger: <b>9 von 12 Benutzern</b>. 2 haben E-Mails abgeschaltet, 1 hat keine E-Mail-Adresse hinterlegt.</p></div>
<span class="btn">Veröffentlichen</span><span class="btn sec">Als Entwurf speichern</span>
</div></div></div></div><div class="frame phone dark"><div class="app rel"><div class="top"><span class="burger"></span><span>ping-mee Portal</span><span class="av">EB</span></div><div class="body"><div class="main">
<h2>Ankündigung verfassen</h2>
<p class="lead">Ihre Ankündigung erscheint auf der Startseite und kann per E-Mail an alle angemeldeten Empfänger gehen.</p>
<div class="card"><div class="field"><label>Titel</label><div class="in" style="color:var(--text)">Geplante Wartung am Samstag</div></div>
<div class="field"><label>Wichtigkeit</label><span class="radio">Information</span><span class="radio sel">Warnung</span><span class="radio">Ausfall</span>
<div class="hint">Bestimmt die Farbe der Meldung auf der Startseite. Ohne Auswahl gilt „Information“.</div></div>
<div class="field"><label>Text</label>
<div class="tb"><span><b>F</b></span><span><i>K</i></span><span>Überschrift</span><span>Liste</span><span>Link</span></div>
<div class="wys">Jellyfin und die <b>Dateifreigabe</b> sind am Samstag von 08:00 bis 12:00 Uhr nicht erreichbar.<ul style="margin:8px 0 0;padding-left:20px"><li>Grund: Umzug auf neue Hardware</li><li>Alles andere läuft normal</li></ul></div>
<div class="hint">So sehen es Benutzer auf der Startseite und in der E-Mail.</div></div></div>
<div class="card"><h3>Weitere Angaben (optional)</h3>
<div class="two"><div class="field"><label>Wartungsfenster: Beginn</label><div class="in">18.10.2026 08:00</div></div><div class="field"><label>Wartungsfenster: Ende</label><div class="in">18.10.2026 12:00</div></div></div>
<div class="hint" style="margin:-6px 0 12px">Zeitzone: Europe/Berlin. Die Zeitzone steht auch in der E-Mail.</div>
<div class="two"><div class="field"><label>Ablaufdatum</label><div class="in">18.10.2026 13:00</div><div class="hint">Danach verschwindet die Ankündigung von der Startseite.</div></div>
<div class="field"><label>Veröffentlichen</label><span class="radio">Sofort</span><span class="radio sel">Geplant</span><div class="in" style="margin-top:6px">17.10.2026 08:00</div></div></div></div>
<div class="card"><h3>E-Mail</h3><div class="radio sel">Benutzer per E-Mail benachrichtigen</div>
<p class="small" style="margin:6px 0 0">Empfänger: <b>9 von 12 Benutzern</b>. 2 haben E-Mails abgeschaltet, 1 hat keine E-Mail-Adresse hinterlegt.</p></div>
<span class="btn">Veröffentlichen</span><span class="btn sec">Als Entwurf speichern</span>
</div></div></div></div></div></body></html>
@@ -0,0 +1,85 @@
<!doctype html><html lang="de"><meta charset="utf-8"><title>Dashboard (Startseite)</title><style>
/* Governing spec sections: EXPERIENCE.md Information Architecture, Component Patterns, State Patterns;
DESIGN.md Colors, Components. Mock only - the spines win on conflict. Offline: no JS, no network.
Brand blue is the Fluent 2 default (#0F6CBD) [ASSUMPTION, verify at scaffold]. German formal (Sie) is the default voice. */
:root{--brand:#0F6CBD;--on-brand:#fff}
*{box-sizing:border-box}
body{margin:0;padding:24px;font-family:"Segoe UI",system-ui,sans-serif;background:#e8e8e8;color:#242424}
h1.cap{font-size:14px;font-weight:600;margin:0 0 4px}
p.cap{font-size:12px;margin:0 0 16px;color:#555;max-width:900px}
.stage{display:flex;gap:32px;align-items:flex-start;flex-wrap:wrap}
.frame{border-radius:10px;overflow:hidden;box-shadow:0 4px 24px rgba(0,0,0,.25);border:1px solid #bbb}
.desk{width:980px}.phone{width:360px;border-radius:28px;border:8px solid #222}
.light{--bg:#fafafa;--card:#fff;--text:#242424;--sub:#616161;--line:#e0e0e0;--nav:#f5f5f5;--warn-bg:#fff9f5;--warn-line:#f7630c;--chip-bg:#fff4ce;--chip-text:#5c4400;--field:#fff;--fieldline:#8a8a8a}
.dark{--bg:#1f1f1f;--card:#292929;--text:#ffffff;--sub:#c8c8c8;--line:#3d3d3d;--nav:#242424;--warn-bg:#3a2a1f;--warn-line:#f7630c;--chip-bg:#4a3b00;--chip-text:#ffe9a8;--field:#333;--fieldline:#9a9a9a;--brand:#479ef5;--on-brand:#0a0a0a}
.app{background:var(--bg);color:var(--text);display:flex;flex-direction:column;min-height:560px}
.top{height:48px;background:var(--brand);color:var(--on-brand);display:flex;align-items:center;justify-content:space-between;padding:0 16px;font-weight:600}
.dark .top{background:#0f2a43;color:#fff}
.av{width:32px;height:32px;border-radius:50%;background:#fff;color:var(--brand);display:grid;place-items:center;font-size:12px;font-weight:700}
.dark .av{background:#479ef5;color:#0a0a0a}
.body{display:flex;flex:1}
.nav{width:220px;background:var(--nav);border-right:1px solid var(--line);padding:12px 8px}
.nav a{display:flex;align-items:center;gap:10px;padding:9px 12px;border-radius:4px;color:var(--text);font-size:14px;margin-bottom:2px}
.nav a.on{background:var(--card);font-weight:600;box-shadow:inset 3px 0 0 var(--brand)}
.nav i{width:14px;height:14px;border-radius:3px;background:var(--sub);opacity:.6;display:inline-block}
.main{flex:1;min-width:0;padding:24px 28px}
.phone .main{padding:16px}
.phone .row{flex-direction:column;gap:2px}
.phone .row span:first-child{width:auto}
.row span:last-child{min-width:0;overflow-wrap:anywhere}
.phone .card{padding:14px}
h2{font-size:24px;font-weight:600;margin:0 0 4px}.phone h2{font-size:20px}
.lead{font-size:14px;color:var(--sub);margin:0 0 18px;max-width:640px}
.card{background:var(--card);border:1px solid var(--line);border-radius:8px;padding:16px 18px;margin-bottom:14px}
.card h3{font-size:16px;margin:0 0 8px}
.banner{border:1px solid var(--warn-line);background:var(--warn-bg);border-left-width:5px;border-radius:6px;padding:14px 16px;margin-bottom:14px;font-size:14px}
.banner b{display:block;margin-bottom:4px}
.grid{display:grid;grid-template-columns:1fr 1fr;gap:14px}.phone .grid{grid-template-columns:1fr}
.row{display:flex;gap:10px;font-size:14px;padding:5px 0;border-bottom:1px solid var(--line)}
.row span:first-child{width:130px;color:var(--sub);flex:none}
.pic{width:72px;height:72px;border-radius:50%;background:var(--brand);color:var(--on-brand);display:grid;place-items:center;font-size:26px;font-weight:600;flex:none}
.btn{display:inline-block;background:var(--brand);color:var(--on-brand);border-radius:4px;padding:8px 16px;font-size:14px;font-weight:600;margin:0 8px 6px 0}
.btn.sec{background:transparent;color:var(--text);border:1px solid var(--fieldline)}
.field{margin-bottom:14px}.field label{display:block;font-size:14px;font-weight:600;margin-bottom:4px}
.field .in{height:34px;border:1px solid var(--fieldline);border-bottom-width:2px;border-radius:4px;background:var(--field);padding:6px 10px;font-size:14px;color:var(--sub)}
.hint{font-size:12px;color:var(--sub);margin-top:4px}
.chip{display:inline-block;background:var(--chip-bg);color:var(--chip-text);border-radius:10px;padding:2px 10px;font-size:12px;font-weight:600}
ul.rules{margin:6px 0 0;padding-left:20px;font-size:13px;color:var(--sub)}
.small{font-size:13px;color:var(--sub)}
.menu{position:absolute;right:16px;top:44px;background:var(--card);color:var(--text);border:1px solid var(--line);border-radius:8px;padding:12px 14px;font-size:13px;width:240px;box-shadow:0 6px 20px rgba(0,0,0,.25)}
.menu div{padding:4px 0}
.rel{position:relative}
.burger{display:inline-block;width:18px;height:2px;background:currentColor;box-shadow:0 6px 0 currentColor,0 -6px 0 currentColor}
</style><body><h1 class="cap">Dashboard (Startseite)</h1><p class="cap">Desktop light with the profile menu open (theme, language, formality). Phone dark: menu collapsed, cards stacked. Illustrates: IA Dashboard, announcement banner (warning), account card including password age.</p><div class="stage"><div class="frame desk light"><div class="app rel"><div class="top"><span>ping-mee Portal</span><span class="av">LM</span></div><div class="body"><div class="nav"><a class="on"><i></i>Startseite</a><a class=""><i></i>Profil</a><a class=""><i></i>Profilbild</a><a class=""><i></i>Passwort</a><a class=""><i></i>Ankündigungen</a><a class=""><i></i>Benachrichtigungen</a></div><div class="main">
<h2>Guten Tag, Lena</h2><p class="lead">Hier sehen Sie aktuelle Hinweise und Ihre Kontodaten auf einen Blick.</p>
<div class="banner"><b>Geplante Wartung: Samstag, 18. Oktober, 08:00–12:00 Uhr (Europe/Berlin)</b>
Jellyfin und die Dateifreigabe sind in dieser Zeit nicht erreichbar. Alles andere läuft normal.</div>
<div class="grid">
<div class="card"><h3>Ihr Konto</h3>
<div style="display:flex;gap:14px;align-items:center;margin-bottom:10px"><div class="pic">LM</div><div><b>Lena Meier</b><div class="small">Benutzername: lmeier</div></div></div>
<div class="row"><span>E-Mail</span><span>lena.meier@example.com</span></div>
<div class="row"><span>Telefon</span><span>+49 170 1234567</span></div>
<div class="row"><span>Passwort zuletzt geändert</span><span>3. September 2026</span></div>
<p style="margin:12px 0 0"><span class="btn sec">Profil bearbeiten</span></p></div>
<div class="card"><h3>Letzte Ankündigungen</h3>
<div class="row"><span>12. Okt.</span><span>Neue Serie in Jellyfin verfügbar</span></div>
<div class="row"><span>29. Sep.</span><span>Server-Umzug abgeschlossen</span></div>
<div class="row"><span>14. Sep.</span><span>Kurze Unterbrechung heute Abend</span></div>
<p style="margin:12px 0 0"><span class="btn sec">Alle Ankündigungen</span></p></div></div>
</div></div><div class="menu"><b>Lena Meier</b><div class="small">lmeier</div><hr style="border:0;border-top:1px solid var(--line)"><div>Design: Hell · Dunkel · <b>System</b></div><div>Sprache: <b>Deutsch</b> · English</div><div>Anrede: <b>Sie</b> · Du</div><hr style="border:0;border-top:1px solid var(--line)"><div>Abmelden</div></div></div></div><div class="frame phone dark"><div class="app rel"><div class="top"><span class="burger"></span><span>ping-mee Portal</span><span class="av">LM</span></div><div class="body"><div class="main">
<h2>Guten Tag, Lena</h2><p class="lead">Hier sehen Sie aktuelle Hinweise und Ihre Kontodaten auf einen Blick.</p>
<div class="banner"><b>Geplante Wartung: Samstag, 18. Oktober, 08:00–12:00 Uhr (Europe/Berlin)</b>
Jellyfin und die Dateifreigabe sind in dieser Zeit nicht erreichbar. Alles andere läuft normal.</div>
<div class="grid">
<div class="card"><h3>Ihr Konto</h3>
<div style="display:flex;gap:14px;align-items:center;margin-bottom:10px"><div class="pic">LM</div><div><b>Lena Meier</b><div class="small">Benutzername: lmeier</div></div></div>
<div class="row"><span>E-Mail</span><span>lena.meier@example.com</span></div>
<div class="row"><span>Telefon</span><span>+49 170 1234567</span></div>
<div class="row"><span>Passwort zuletzt geändert</span><span>3. September 2026</span></div>
<p style="margin:12px 0 0"><span class="btn sec">Profil bearbeiten</span></p></div>
<div class="card"><h3>Letzte Ankündigungen</h3>
<div class="row"><span>12. Okt.</span><span>Neue Serie in Jellyfin verfügbar</span></div>
<div class="row"><span>29. Sep.</span><span>Server-Umzug abgeschlossen</span></div>
<div class="row"><span>14. Sep.</span><span>Kurze Unterbrechung heute Abend</span></div>
<p style="margin:12px 0 0"><span class="btn sec">Alle Ankündigungen</span></p></div></div>
</div></div></div></div></div></body></html>
@@ -0,0 +1,73 @@
<!doctype html><html lang="de"><meta charset="utf-8"><title>Passwort ändern</title><style>
/* Governing spec sections: EXPERIENCE.md Information Architecture, Component Patterns, State Patterns;
DESIGN.md Colors, Components. Mock only - the spines win on conflict. Offline: no JS, no network.
Brand blue is the Fluent 2 default (#0F6CBD) [ASSUMPTION, verify at scaffold]. German formal (Sie) is the default voice. */
:root{--brand:#0F6CBD;--on-brand:#fff}
*{box-sizing:border-box}
body{margin:0;padding:24px;font-family:"Segoe UI",system-ui,sans-serif;background:#e8e8e8;color:#242424}
h1.cap{font-size:14px;font-weight:600;margin:0 0 4px}
p.cap{font-size:12px;margin:0 0 16px;color:#555;max-width:900px}
.stage{display:flex;gap:32px;align-items:flex-start;flex-wrap:wrap}
.frame{border-radius:10px;overflow:hidden;box-shadow:0 4px 24px rgba(0,0,0,.25);border:1px solid #bbb}
.desk{width:980px}.phone{width:360px;border-radius:28px;border:8px solid #222}
.light{--bg:#fafafa;--card:#fff;--text:#242424;--sub:#616161;--line:#e0e0e0;--nav:#f5f5f5;--warn-bg:#fff9f5;--warn-line:#f7630c;--chip-bg:#fff4ce;--chip-text:#5c4400;--field:#fff;--fieldline:#8a8a8a}
.dark{--bg:#1f1f1f;--card:#292929;--text:#ffffff;--sub:#c8c8c8;--line:#3d3d3d;--nav:#242424;--warn-bg:#3a2a1f;--warn-line:#f7630c;--chip-bg:#4a3b00;--chip-text:#ffe9a8;--field:#333;--fieldline:#9a9a9a;--brand:#479ef5;--on-brand:#0a0a0a}
.app{background:var(--bg);color:var(--text);display:flex;flex-direction:column;min-height:560px}
.top{height:48px;background:var(--brand);color:var(--on-brand);display:flex;align-items:center;justify-content:space-between;padding:0 16px;font-weight:600}
.dark .top{background:#0f2a43;color:#fff}
.av{width:32px;height:32px;border-radius:50%;background:#fff;color:var(--brand);display:grid;place-items:center;font-size:12px;font-weight:700}
.dark .av{background:#479ef5;color:#0a0a0a}
.body{display:flex;flex:1}
.nav{width:220px;background:var(--nav);border-right:1px solid var(--line);padding:12px 8px}
.nav a{display:flex;align-items:center;gap:10px;padding:9px 12px;border-radius:4px;color:var(--text);font-size:14px;margin-bottom:2px}
.nav a.on{background:var(--card);font-weight:600;box-shadow:inset 3px 0 0 var(--brand)}
.nav i{width:14px;height:14px;border-radius:3px;background:var(--sub);opacity:.6;display:inline-block}
.main{flex:1;min-width:0;padding:24px 28px}
.phone .main{padding:16px}
.phone .row{flex-direction:column;gap:2px}
.phone .row span:first-child{width:auto}
.row span:last-child{min-width:0;overflow-wrap:anywhere}
.phone .card{padding:14px}
h2{font-size:24px;font-weight:600;margin:0 0 4px}.phone h2{font-size:20px}
.lead{font-size:14px;color:var(--sub);margin:0 0 18px;max-width:640px}
.card{background:var(--card);border:1px solid var(--line);border-radius:8px;padding:16px 18px;margin-bottom:14px}
.card h3{font-size:16px;margin:0 0 8px}
.banner{border:1px solid var(--warn-line);background:var(--warn-bg);border-left-width:5px;border-radius:6px;padding:14px 16px;margin-bottom:14px;font-size:14px}
.banner b{display:block;margin-bottom:4px}
.grid{display:grid;grid-template-columns:1fr 1fr;gap:14px}.phone .grid{grid-template-columns:1fr}
.row{display:flex;gap:10px;font-size:14px;padding:5px 0;border-bottom:1px solid var(--line)}
.row span:first-child{width:130px;color:var(--sub);flex:none}
.pic{width:72px;height:72px;border-radius:50%;background:var(--brand);color:var(--on-brand);display:grid;place-items:center;font-size:26px;font-weight:600;flex:none}
.btn{display:inline-block;background:var(--brand);color:var(--on-brand);border-radius:4px;padding:8px 16px;font-size:14px;font-weight:600;margin:0 8px 6px 0}
.btn.sec{background:transparent;color:var(--text);border:1px solid var(--fieldline)}
.field{margin-bottom:14px}.field label{display:block;font-size:14px;font-weight:600;margin-bottom:4px}
.field .in{height:34px;border:1px solid var(--fieldline);border-bottom-width:2px;border-radius:4px;background:var(--field);padding:6px 10px;font-size:14px;color:var(--sub)}
.hint{font-size:12px;color:var(--sub);margin-top:4px}
.chip{display:inline-block;background:var(--chip-bg);color:var(--chip-text);border-radius:10px;padding:2px 10px;font-size:12px;font-weight:600}
ul.rules{margin:6px 0 0;padding-left:20px;font-size:13px;color:var(--sub)}
.small{font-size:13px;color:var(--sub)}
.menu{position:absolute;right:16px;top:44px;background:var(--card);color:var(--text);border:1px solid var(--line);border-radius:8px;padding:12px 14px;font-size:13px;width:240px;box-shadow:0 6px 20px rgba(0,0,0,.25)}
.menu div{padding:4px 0}
.rel{position:relative}
.burger{display:inline-block;width:18px;height:2px;background:currentColor;box-shadow:0 6px 0 currentColor,0 -6px 0 currentColor}
</style><body><h1 class="cap">Passwort ändern</h1><p class="cap">Desktop light, phone dark. Rules shown before typing; effect of the change stated up front; forgotten or expired passwords point to the Admin. Illustrates: password flow (UJ-1). The listed rules are sample text; the real ones must come from the domain policy.</p><div class="stage"><div class="frame desk light"><div class="app rel"><div class="top"><span>ping-mee Portal</span><span class="av">LM</span></div><div class="body"><div class="nav"><a class=""><i></i>Startseite</a><a class=""><i></i>Profil</a><a class=""><i></i>Profilbild</a><a class="on"><i></i>Passwort</a><a class=""><i></i>Ankündigungen</a><a class=""><i></i>Benachrichtigungen</a></div><div class="main">
<h2>Passwort ändern</h2>
<p class="lead">Dieses Passwort gilt für alle ping-mee-Dienste. Nach der Änderung werden Sie auf anderen Geräten abgemeldet, und Sie erhalten eine Bestätigung per E-Mail.</p>
<div class="card" style="max-width:520px">
<div class="field"><label>Aktuelles Passwort</label><div class="in">••••••••••</div><div class="hint">Wir prüfen es, bevor etwas geändert wird.</div></div>
<div class="field"><label>Neues Passwort</label><div class="in">&nbsp;</div>
<ul class="rules"><li>Mindestens 12 Zeichen</li><li>Groß- und Kleinbuchstaben, eine Zahl</li><li>Nicht eines Ihrer letzten Passwörter</li></ul></div>
<div class="field"><label>Neues Passwort wiederholen</label><div class="in">&nbsp;</div></div>
<span class="btn">Passwort ändern</span><span class="btn sec">Abbrechen</span></div>
<p class="small">Passwort vergessen oder abgelaufen? Wenden Sie sich bitte an den Administrator.</p>
</div></div></div></div><div class="frame phone dark"><div class="app rel"><div class="top"><span class="burger"></span><span>ping-mee Portal</span><span class="av">LM</span></div><div class="body"><div class="main">
<h2>Passwort ändern</h2>
<p class="lead">Dieses Passwort gilt für alle ping-mee-Dienste. Nach der Änderung werden Sie auf anderen Geräten abgemeldet, und Sie erhalten eine Bestätigung per E-Mail.</p>
<div class="card" style="max-width:520px">
<div class="field"><label>Aktuelles Passwort</label><div class="in">••••••••••</div><div class="hint">Wir prüfen es, bevor etwas geändert wird.</div></div>
<div class="field"><label>Neues Passwort</label><div class="in">&nbsp;</div>
<ul class="rules"><li>Mindestens 12 Zeichen</li><li>Groß- und Kleinbuchstaben, eine Zahl</li><li>Nicht eines Ihrer letzten Passwörter</li></ul></div>
<div class="field"><label>Neues Passwort wiederholen</label><div class="in">&nbsp;</div></div>
<span class="btn">Passwort ändern</span><span class="btn sec">Abbrechen</span></div>
<p class="small">Passwort vergessen oder abgelaufen? Wenden Sie sich bitte an den Administrator.</p>
</div></div></div></div></div></body></html>
@@ -0,0 +1,69 @@
<!doctype html><html lang="de"><meta charset="utf-8"><title>Profilbild - Pending state</title><style>
/* Governing spec sections: EXPERIENCE.md Information Architecture, Component Patterns, State Patterns;
DESIGN.md Colors, Components. Mock only - the spines win on conflict. Offline: no JS, no network.
Brand blue is the Fluent 2 default (#0F6CBD) [ASSUMPTION, verify at scaffold]. German formal (Sie) is the default voice. */
:root{--brand:#0F6CBD;--on-brand:#fff}
*{box-sizing:border-box}
body{margin:0;padding:24px;font-family:"Segoe UI",system-ui,sans-serif;background:#e8e8e8;color:#242424}
h1.cap{font-size:14px;font-weight:600;margin:0 0 4px}
p.cap{font-size:12px;margin:0 0 16px;color:#555;max-width:900px}
.stage{display:flex;gap:32px;align-items:flex-start;flex-wrap:wrap}
.frame{border-radius:10px;overflow:hidden;box-shadow:0 4px 24px rgba(0,0,0,.25);border:1px solid #bbb}
.desk{width:980px}.phone{width:360px;border-radius:28px;border:8px solid #222}
.light{--bg:#fafafa;--card:#fff;--text:#242424;--sub:#616161;--line:#e0e0e0;--nav:#f5f5f5;--warn-bg:#fff9f5;--warn-line:#f7630c;--chip-bg:#fff4ce;--chip-text:#5c4400;--field:#fff;--fieldline:#8a8a8a}
.dark{--bg:#1f1f1f;--card:#292929;--text:#ffffff;--sub:#c8c8c8;--line:#3d3d3d;--nav:#242424;--warn-bg:#3a2a1f;--warn-line:#f7630c;--chip-bg:#4a3b00;--chip-text:#ffe9a8;--field:#333;--fieldline:#9a9a9a;--brand:#479ef5;--on-brand:#0a0a0a}
.app{background:var(--bg);color:var(--text);display:flex;flex-direction:column;min-height:560px}
.top{height:48px;background:var(--brand);color:var(--on-brand);display:flex;align-items:center;justify-content:space-between;padding:0 16px;font-weight:600}
.dark .top{background:#0f2a43;color:#fff}
.av{width:32px;height:32px;border-radius:50%;background:#fff;color:var(--brand);display:grid;place-items:center;font-size:12px;font-weight:700}
.dark .av{background:#479ef5;color:#0a0a0a}
.body{display:flex;flex:1}
.nav{width:220px;background:var(--nav);border-right:1px solid var(--line);padding:12px 8px}
.nav a{display:flex;align-items:center;gap:10px;padding:9px 12px;border-radius:4px;color:var(--text);font-size:14px;margin-bottom:2px}
.nav a.on{background:var(--card);font-weight:600;box-shadow:inset 3px 0 0 var(--brand)}
.nav i{width:14px;height:14px;border-radius:3px;background:var(--sub);opacity:.6;display:inline-block}
.main{flex:1;min-width:0;padding:24px 28px}
.phone .main{padding:16px}
.phone .row{flex-direction:column;gap:2px}
.phone .row span:first-child{width:auto}
.row span:last-child{min-width:0;overflow-wrap:anywhere}
.phone .card{padding:14px}
h2{font-size:24px;font-weight:600;margin:0 0 4px}.phone h2{font-size:20px}
.lead{font-size:14px;color:var(--sub);margin:0 0 18px;max-width:640px}
.card{background:var(--card);border:1px solid var(--line);border-radius:8px;padding:16px 18px;margin-bottom:14px}
.card h3{font-size:16px;margin:0 0 8px}
.banner{border:1px solid var(--warn-line);background:var(--warn-bg);border-left-width:5px;border-radius:6px;padding:14px 16px;margin-bottom:14px;font-size:14px}
.banner b{display:block;margin-bottom:4px}
.grid{display:grid;grid-template-columns:1fr 1fr;gap:14px}.phone .grid{grid-template-columns:1fr}
.row{display:flex;gap:10px;font-size:14px;padding:5px 0;border-bottom:1px solid var(--line)}
.row span:first-child{width:130px;color:var(--sub);flex:none}
.pic{width:72px;height:72px;border-radius:50%;background:var(--brand);color:var(--on-brand);display:grid;place-items:center;font-size:26px;font-weight:600;flex:none}
.btn{display:inline-block;background:var(--brand);color:var(--on-brand);border-radius:4px;padding:8px 16px;font-size:14px;font-weight:600;margin:0 8px 6px 0}
.btn.sec{background:transparent;color:var(--text);border:1px solid var(--fieldline)}
.field{margin-bottom:14px}.field label{display:block;font-size:14px;font-weight:600;margin-bottom:4px}
.field .in{height:34px;border:1px solid var(--fieldline);border-bottom-width:2px;border-radius:4px;background:var(--field);padding:6px 10px;font-size:14px;color:var(--sub)}
.hint{font-size:12px;color:var(--sub);margin-top:4px}
.chip{display:inline-block;background:var(--chip-bg);color:var(--chip-text);border-radius:10px;padding:2px 10px;font-size:12px;font-weight:600}
ul.rules{margin:6px 0 0;padding-left:20px;font-size:13px;color:var(--sub)}
.small{font-size:13px;color:var(--sub)}
.menu{position:absolute;right:16px;top:44px;background:var(--card);color:var(--text);border:1px solid var(--line);border-radius:8px;padding:12px 14px;font-size:13px;width:240px;box-shadow:0 6px 20px rgba(0,0,0,.25)}
.menu div{padding:4px 0}
.rel{position:relative}
.burger{display:inline-block;width:18px;height:2px;background:currentColor;box-shadow:0 6px 0 currentColor,0 -6px 0 currentColor}
</style><body><h1 class="cap">Profilbild - Pending state</h1><p class="cap">Desktop light, phone dark. Pending chip plus the two-hour note; the current picture stays until the sync has run. Illustrates: picture flow (UJ-2), pending state.</p><div class="stage"><div class="frame desk light"><div class="app rel"><div class="top"><span>ping-mee Portal</span><span class="av">LM</span></div><div class="body"><div class="nav"><a class=""><i></i>Startseite</a><a class=""><i></i>Profil</a><a class="on"><i></i>Profilbild</a><a class=""><i></i>Passwort</a><a class=""><i></i>Ankündigungen</a><a class=""><i></i>Benachrichtigungen</a></div><div class="main">
<h2>Profilbild</h2>
<p class="lead">Ihr Bild erscheint in den ping-mee-Diensten, zum Beispiel in Jellyfin.</p>
<div class="card" style="max-width:560px">
<div style="display:flex;gap:16px;align-items:center;margin-bottom:12px"><div class="pic" style="width:96px;height:96px;font-size:34px">LM</div>
<div><span class="chip">Wird aktualisiert</span><p class="small" style="margin:8px 0 0">Ihr neues Bild wurde gespeichert. Es erscheint innerhalb von etwa 2 Stunden. Bis dahin sehen Sie hier noch das bisherige Bild.</p></div></div>
<span class="btn">Neues Bild auswählen</span><span class="btn sec">Bild entfernen</span>
<div class="hint" style="margin-top:10px">Erlaubt sind JPEG, PNG, HEIC und WebP bis 10 MB. Sie können das Bild vor dem Speichern zuschneiden.</div></div>
</div></div></div></div><div class="frame phone dark"><div class="app rel"><div class="top"><span class="burger"></span><span>ping-mee Portal</span><span class="av">LM</span></div><div class="body"><div class="main">
<h2>Profilbild</h2>
<p class="lead">Ihr Bild erscheint in den ping-mee-Diensten, zum Beispiel in Jellyfin.</p>
<div class="card" style="max-width:560px">
<div style="display:flex;gap:16px;align-items:center;margin-bottom:12px"><div class="pic" style="width:96px;height:96px;font-size:34px">LM</div>
<div><span class="chip">Wird aktualisiert</span><p class="small" style="margin:8px 0 0">Ihr neues Bild wurde gespeichert. Es erscheint innerhalb von etwa 2 Stunden. Bis dahin sehen Sie hier noch das bisherige Bild.</p></div></div>
<span class="btn">Neues Bild auswählen</span><span class="btn sec">Bild entfernen</span>
<div class="hint" style="margin-top:10px">Erlaubt sind JPEG, PNG, HEIC und WebP bis 10 MB. Sie können das Bild vor dem Speichern zuschneiden.</div></div>
</div></div></div></div></div></body></html>