From 677956ed9de14a32b13b503811ae07346b87c5a6 Mon Sep 17 00:00:00 2001 From: Max Rink Date: Sun, 17 May 2026 01:19:09 +0200 Subject: [PATCH 01/25] feat(ui): standardize accessibility, contrast, and spacing --- frontend/src/App.vue | 44 +---- frontend/src/assets/base.css | 165 ++++++++++++++---- .../src/components/ApprovalModalContent.vue | 6 +- .../src/components/BreakglassSessionCard.vue | 4 +- frontend/src/components/DebugPanel.vue | 10 +- frontend/src/components/DebugSessionCard.vue | 141 +++++++++------ .../src/components/SessionSummaryCard.vue | 2 +- .../src/components/WithdrawConfirmDialog.vue | 9 +- frontend/src/views/BreakglassView.vue | 1 + frontend/src/views/DebugSessionBrowser.vue | 60 ++----- frontend/src/views/DebugSessionCreate.vue | 10 +- frontend/src/views/DebugSessionDetails.vue | 19 +- frontend/src/views/PendingApprovalsView.vue | 10 +- 13 files changed, 274 insertions(+), 207 deletions(-) diff --git a/frontend/src/App.vue b/frontend/src/App.vue index adb10a4ba..ceb75d9bb 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -28,7 +28,7 @@ const router = useRouter(); const groupsRef = ref([]); -// Theme handling respects the user's system preference but offers a manual override +// Theme handling respects the user's system preference without offering a manual toggle const theme = ref<"light" | "dark">(getInitialTheme()); let mediaQuery: MediaQueryList | null = null; let mediaQueryHandler: ((event: MediaQueryListEvent) => void) | null = null; @@ -83,20 +83,9 @@ function getInitialTheme(): "light" | "dark" { if (typeof window === "undefined") { return "light"; } - try { - const stored = localStorage.getItem("breakglass-theme"); - if (stored === "light" || stored === "dark") return stored; - } catch {} return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; } -function toggleTheme() { - theme.value = theme.value === "dark" ? "light" : "dark"; - try { - localStorage.setItem("breakglass-theme", theme.value); - } catch {} -} - function applyTheme(value: "light" | "dark") { if (typeof document !== "undefined") { document.documentElement.setAttribute("data-theme", value); @@ -463,7 +452,7 @@ watch( :aria-pressed="highContrast" @click="toggleHighContrast" > - + @@ -574,23 +563,12 @@ scale-telekom-header::part(app-name-text) { font: var(--telekom-text-style-heading-6); } -.header-functions-container { +.hc-toggle-nav-item { display: flex; align-items: center; - gap: var(--space-md); } -.theme-utilities { - display: flex; - align-items: center; - gap: var(--space-xs); - padding: 0 var(--space-md); - border-right: 1px solid var(--telekom-color-ui-border-standard); - margin-right: var(--space-sm); -} - -.hc-toggle-button, -.theme-toggle-button { +.hc-toggle-button { display: inline-flex; align-items: center; justify-content: center; @@ -604,15 +582,8 @@ scale-telekom-header::part(app-name-text) { transition: all var(--telekom-motion-duration-transition, 200ms) var(--telekom-motion-easing-standard); } -.hc-toggle-button:hover, -.theme-toggle-button:hover { - background-color: var(--telekom-color-ui-subtle); - transform: translateY(-1px); -} - -.hc-toggle-button:active, -.theme-toggle-button:active { - transform: translateY(0) scale(0.95); +.hc-toggle-button:hover { + background-color: var(--surface-card-subtle); } .hc-toggle-button.hc-active { @@ -700,8 +671,5 @@ scale-telekom-header::part(app-name-text) { .mobile-nav-item { display: flex; } - .theme-utilities { - display: none; /* Hide on mobile header, use flyout menu instead */ - } } diff --git a/frontend/src/assets/base.css b/frontend/src/assets/base.css index e178c7105..1980fa704 100644 --- a/frontend/src/assets/base.css +++ b/frontend/src/assets/base.css @@ -20,6 +20,11 @@ background-color: var(--surface-primary); color-scheme: light; + /* AAA-compliant overrides for Telekom tokens that miss 7:1 on subtle backgrounds. + * Scale sets this to hsla(0,0%,0%,0.65) ≈ #595959 on :root in its own stylesheet, + * which loads after ours. !important is required to ensure our solid override wins. */ + --telekom-color-text-and-icon-additional: #4a4a4a !important; + /* ============================================ * SEMANTIC SPACING ALIASES * Maps Scale spacing tokens to semantic names @@ -28,12 +33,12 @@ /* Component internal spacing */ --space-2xs: var(--scl-spacing-2, 0.125rem); /* 2px - hairline gaps */ --space-xs: var(--scl-spacing-4, 0.25rem); /* 4px - tight internal padding */ - --space-sm: var(--scl-spacing-12, 0.75rem); /* 12px - small gaps, chip padding */ - --space-md: var(--scl-spacing-16, 1rem); /* 16px - default internal gaps */ - --space-lg: var(--scl-spacing-24, 1.5rem); /* 24px - section gaps */ - --space-xl: var(--scl-spacing-32, 2rem); /* 32px - large gaps, card padding */ - --space-2xl: var(--scl-spacing-48, 3rem); /* 48px - section separation */ - --space-3xl: var(--scl-spacing-64, 4rem); /* 64px - page-level gaps */ + --space-sm: var(--scl-spacing-8, 0.5rem); /* 8px - small gaps, chip padding */ + --space-md: var(--scl-spacing-12, 0.75rem); /* 12px - default internal gaps */ + --space-lg: var(--scl-spacing-16, 1rem); /* 16px - section gaps */ + --space-xl: var(--scl-spacing-24, 1.5rem); /* 24px - large gaps, card padding */ + --space-2xl: var(--scl-spacing-32, 2rem); /* 32px - section separation */ + --space-3xl: var(--scl-spacing-48, 3rem); /* 48px - page-level gaps */ /* Card & Container padding */ --card-padding: var(--scl-spacing-24, 1.5rem); @@ -41,11 +46,11 @@ --card-gap: var(--scl-spacing-16, 1rem); /* Stack & flow gaps — @deprecated Use --space-* instead (same values, one scale to learn) */ - --stack-gap-xs: var(--scl-spacing-8, 0.5rem); - --stack-gap-sm: var(--scl-spacing-12, 0.75rem); - --stack-gap-md: var(--scl-spacing-16, 1rem); - --stack-gap-lg: var(--scl-spacing-24, 1.5rem); - --stack-gap-xl: var(--scl-spacing-32, 2rem); + --stack-gap-xs: var(--scl-spacing-4, 0.25rem); + --stack-gap-sm: var(--scl-spacing-8, 0.5rem); + --stack-gap-md: var(--scl-spacing-12, 0.75rem); + --stack-gap-lg: var(--scl-spacing-16, 1rem); + --stack-gap-xl: var(--scl-spacing-24, 1.5rem); /* Grid gaps */ --grid-gap: var(--scl-spacing-16, 1rem); @@ -122,6 +127,11 @@ --tone-chip-neutral-bg: var(--telekom-color-ui-subtle, #efeff0); --tone-chip-neutral-border: var(--telekom-color-ui-border-standard); --tone-chip-neutral-text: var(--telekom-color-text-and-icon-standard); + + /* Brand tone chips — AAA compliant magenta for info/brand callouts */ + --tone-chip-brand-bg: #ffd9e5; + --tone-chip-brand-border: #e20074; + --tone-chip-brand-text: #8e004a; } /* ============================================ @@ -132,7 +142,7 @@ :root[data-theme="dark"] { color-scheme: dark; - /* Native scale tokens will be inherited for dark mode */ + --telekom-color-text-and-icon-additional: #c0c0c0 !important; /* Strengthen borders for dark mode — Scale default is rgba(255,255,255,0.4) * which is barely visible on dark backgrounds. Use 55% white for clearer boundaries. */ @@ -179,6 +189,13 @@ /* Surface helpers for dark mode sections */ --surface-card-subtle: #242426; + + /* Fix: Lower contrast for expired text in dark mode — boost to AAA */ + --tone-chip-neutral-text: #e0e0e0; + + --tone-chip-brand-bg: #3d0026; + --tone-chip-brand-border: #e20074; + --tone-chip-brand-text: #ff8cc8; } /* ============================================ @@ -200,18 +217,37 @@ --border-strong: #000000; --telekom-color-ui-border-standard: #595959; - /* Enhanced tone chip text — darker for better contrast on subtle backgrounds */ - --tone-chip-info-text: #0d1570; - --tone-chip-success-text: #003d26; - --tone-chip-warning-text: #6b3300; - --tone-chip-danger-text: #8a0700; + /* Enhanced tone chip text — strictly black for maximum contrast on light backgrounds */ + --tone-chip-info-text: #000000; + --tone-chip-success-text: #000000; + --tone-chip-warning-text: #000000; + --tone-chip-danger-text: #000000; --tone-chip-neutral-text: #000000; - --chip-text: #730040; - --chip-bg: color-mix(in srgb, #730040 10%, transparent); + /* Force solid bright backgrounds in HC mode to avoid "color-on-color" confusion */ + --tone-chip-info-bg: #dce1ff; + --tone-chip-success-bg: #b6fcc8; + --tone-chip-warning-bg: #ffe4cc; + --tone-chip-danger-bg: #ffdad6; + + --chip-text: #000000; + --chip-bg: #ffd9e5; /* Solid light magenta for HC */ + + --tone-chip-brand-bg: #ffd9e5; + --tone-chip-brand-text: #000000; + --tone-chip-brand-border: #000000; /* Stronger focus indicator */ --focus-outline: #000000; + + /* Fix: Blue links on black in HC mode have poor contrast (~4.5:1). + * Use a darker blue for HC light to ensure readability on subtle gray backgrounds. */ + --telekom-color-text-and-icon-link-standard: #0055cc; + + /* Strictly high-contrast buttons for HC mode */ + --cta-bg: #000000; + --cta-bg-hover: #1a1a1a; + --telekom-color-text-and-icon-white: #ffffff; } /* High contrast + dark mode combination */ @@ -230,20 +266,36 @@ * to guarantee enhanced contrast on the same dark tone backgrounds. * info #ccd8ff on #131f7b = 9.8:1, success #80f0c0 on #004829 = 7.7:1, * warning #ffd9b3 on #642d07 = 8.2:1, danger #ffcec7 on #5d0d06 = 9.8:1 */ - --tone-chip-info-text: #ccd8ff; - --tone-chip-success-text: #80f0c0; - --tone-chip-warning-text: #ffd9b3; - --tone-chip-danger-text: #ffcec7; + /* AAA 7:1 minimum — strictly white for maximum contrast on dark backgrounds */ + --tone-chip-info-text: #ffffff; + --tone-chip-success-text: #ffffff; + --tone-chip-warning-text: #ffffff; + --tone-chip-danger-text: #ffffff; --tone-chip-neutral-text: #ffffff; - --chip-text: #ffa4cc; - --chip-bg: #4a0030; + --chip-text: #ffffff; + --chip-bg: #5c003c; + + /* Force solid opaque backgrounds in HC dark so axe-core can compute contrast. + * Darker solid versions of the dark-mode defaults to ensure white text passes. */ + --tone-chip-info-bg: #0d1570; + --tone-chip-success-bg: #003d21; + --tone-chip-warning-bg: #4d2200; + --tone-chip-danger-bg: #4a0a04; + + --tone-chip-brand-bg: #5c003c; + --tone-chip-brand-text: #ffffff; + --tone-chip-brand-border: #ffffff; --focus-outline: #ffffff; - /* Scale's default link blue has poor contrast on dark backgrounds (~4.5:1). - * Use a lighter blue that meets AAA (>7:1) on typical HC dark surfaces. */ - --telekom-color-text-and-icon-link-standard: #66b3ff; + /* Fix: Blue links in dark HC mode - use lighter blue for AAA on dark bg */ + --telekom-color-text-and-icon-link-standard: #93c5ff; + + /* Strictly high-contrast buttons for Dark HC mode */ + --cta-bg: #ffffff; + --cta-bg-hover: #e0e0e0; + --telekom-color-text-and-icon-white: #000000; } /* ============================================ @@ -313,6 +365,18 @@ scale-dropdown::part(combobox-label) { background-color: #e20074 !important; } +:root[data-high-contrast="true"] scale-button::part(variant-primary) { + background-color: #000000 !important; + color: #ffffff !important; + border: 2px solid #000000 !important; +} + +:root[data-high-contrast="true"][data-theme="dark"] scale-button::part(variant-primary) { + background-color: #ffffff !important; + color: #000000 !important; + border: 2px solid #ffffff !important; +} + /* AAA contrast: lighten ghost button text in dark mode for 7:1 on dark bg */ :root[data-theme="dark"] scale-button::part(variant-ghost) { color: #93a8ff !important; @@ -478,7 +542,13 @@ scale-modal { scale-modal::part(body) { display: flex; flex-direction: column; - gap: var(--space-md); + gap: var(--space-lg); +} + +scale-modal::part(header) { + border-bottom: 1px solid var(--telekom-color-ui-border-standard); + margin-bottom: var(--space-md); + padding-bottom: var(--space-md); } /* ============================================ @@ -658,7 +728,7 @@ select:focus { display: flex; gap: var(--space-sm); font: var(--telekom-text-style-caption); - color: var(--telekom-color-text-and-icon-standard); + color: var(--telekom-color-text-and-icon-additional); } /* ============================================ @@ -1168,6 +1238,41 @@ select:focus { } } +/* ============================================ + * MODAL & FORM ACTIONS + * Standardized action bar for consistent alignment + * ============================================ */ +.modal-actions, +.dialog-actions, +.form-actions { + display: flex; + flex-wrap: wrap; + justify-content: flex-end; + gap: var(--space-md); + margin-top: var(--space-lg); + padding-top: var(--space-md); + border-top: 1px solid var(--telekom-color-ui-border-standard); +} + +.modal-actions scale-button, +.dialog-actions scale-button, +.form-actions scale-button { + min-width: 8rem; +} + +@media (max-width: 640px) { + .modal-actions, + .dialog-actions, + .form-actions { + flex-direction: column; + } + .modal-actions scale-button, + .dialog-actions scale-button, + .form-actions scale-button { + width: 100%; + } +} + /* ============================================ * REDUCED MOTION * Respect user preference for reduced motion (WCAG 2.3.3). diff --git a/frontend/src/components/ApprovalModalContent.vue b/frontend/src/components/ApprovalModalContent.vue index b8f02c9ad..3099dd7a8 100644 --- a/frontend/src/components/ApprovalModalContent.vue +++ b/frontend/src/components/ApprovalModalContent.vue @@ -190,9 +190,9 @@ function handleNoteChange(ev: Event) { font-weight: 600; text-transform: uppercase; font: var(--telekom-text-style-caption); - background: var(--telekom-color-additional-violet-subtle); - color: var(--telekom-color-text-and-icon-on-subtle-violet); - border: 1px solid color-mix(in srgb, var(--telekom-color-additional-violet-standard) 35%, transparent); + background: var(--tone-chip-brand-bg); + color: var(--tone-chip-brand-text); + border: 1px solid var(--tone-chip-brand-border); } .modal-pill.tone-info { diff --git a/frontend/src/components/BreakglassSessionCard.vue b/frontend/src/components/BreakglassSessionCard.vue index 73afb6e77..6ce9dce08 100644 --- a/frontend/src/components/BreakglassSessionCard.vue +++ b/frontend/src/components/BreakglassSessionCard.vue @@ -290,7 +290,7 @@ const requestReasonText = computed(() => { font: var(--telekom-text-style-caption); text-transform: uppercase; letter-spacing: 0.08em; - color: var(--telekom-color-text-and-icon-standard); + color: var(--telekom-color-text-and-icon-additional); } .session-card__reason p { @@ -343,7 +343,7 @@ const requestReasonText = computed(() => { } .timeline-item--muted { - color: var(--telekom-color-text-and-icon-standard); + color: var(--telekom-color-text-and-icon-additional); } .timeline-item .label { diff --git a/frontend/src/components/DebugPanel.vue b/frontend/src/components/DebugPanel.vue index b69ee513d..fbf49c60a 100644 --- a/frontend/src/components/DebugPanel.vue +++ b/frontend/src/components/DebugPanel.vue @@ -193,7 +193,7 @@ const groupsDisplay = computed(() => { Yes @@ -201,7 +201,7 @@ const groupsDisplay = computed(() => { No @@ -391,11 +391,11 @@ const groupsDisplay = computed(() => { } .debug-item .value.groups-list { - color: var(--telekom-color-functional-success-standard); + color: var(--tone-chip-success-text); } .debug-item .value.mock-indicator { - color: var(--telekom-color-functional-warning-standard); + color: var(--tone-chip-warning-text); font-style: italic; } @@ -475,7 +475,7 @@ const groupsDisplay = computed(() => { } .debug-section.error { - border-left-color: var(--telekom-color-functional-danger-standard); + border-left-color: var(--accent-critical); background-color: var(--tone-chip-danger-bg); border-color: var(--tone-chip-danger-border); } diff --git a/frontend/src/components/DebugSessionCard.vue b/frontend/src/components/DebugSessionCard.vue index 2434a352a..5142d2ada 100644 --- a/frontend/src/components/DebugSessionCard.vue +++ b/frontend/src/components/DebugSessionCard.vue @@ -94,35 +94,35 @@ function handleRenew() {
- + {{ session.statusMessage }}
-
-
+
+
Template {{ session.templateRef || "—" }}
-
+
Requested By {{ session.requestedByDisplayName || session.requestedBy || "—" }}
-
+
Started {{ formatDateTime(session.startsAt) }}
-
+
Expires {{ expiresIn }}
-
+
Participants {{ session.participants ?? "—" }}
-
+
Debug Pods {{ session.allowedPods }}
@@ -131,56 +131,64 @@ function handleRenew() {
- View Details + Details - - Join Session - +
+ + Join + - - Leave - + + Leave + - - Renew - + + Renew + - - Terminate - + + Terminate + - - Approve - + + Approve + - - Reject - + + Reject + +
@@ -192,7 +200,9 @@ function handleRenew() { > -
+