Files

892 lines
41 KiB
CSS
Raw Permalink Normal View History

2026-02-04 23:31:53 +01:00
@charset "UTF-8";
@layer tokens {
2026-02-04 23:31:53 +01:00
:host,
:root {
--app-font-family-emoji:
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--app-font-family-sans-serif:
system-ui, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, Helvetica, Arial,
"Helvetica Neue", sans-serif, var(--app-font-family-emoji);
--app-font-family-monospace:
ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono",
monospace, var(--app-font-family-emoji);
--app-font-family: var(--app-font-family-sans-serif);
--app-line-height: 1.5;
--app-font-weight: 400;
--app-font-size: var(--text-base);
2026-02-04 23:31:53 +01:00
--app-text-underline-offset: 0.1rem;
--app-border-radius: 0.5rem;
--app-border-width: 0.0625rem;
--app-outline-width: 0.125rem;
--app-transition: 0.2s ease-in-out;
--app-spacing: 1rem;
--app-typography-spacing-vertical: 1rem;
--app-block-spacing-vertical: 1.5rem;
--app-block-spacing-horizontal: 1.5rem;
--app-grid-column-gap: var(--app-spacing);
--app-grid-row-gap: var(--app-spacing);
--app-flex-gap: var(--app-spacing);
2026-02-11 19:28:12 +01:00
--app-form-element-spacing-vertical: 0.5rem;
--app-form-element-spacing-horizontal: 0.7rem;
2026-02-04 23:31:53 +01:00
--app-button-padding-vertical: var(--app-form-element-spacing-vertical);
--app-button-padding-horizontal: var(--app-form-element-spacing-horizontal);
--app-button-padding-vertical-small: calc(
var(--app-form-element-spacing-vertical) * 0.3
);
--app-button-padding-horizontal-small: calc(
var(--app-form-element-spacing-horizontal) * 0.5
);
--app-group-box-shadow: 0 0 0 rgba(0, 0, 0, 0);
--app-group-box-shadow-focus-with-button: 0 0 0 var(--app-outline-width)
var(--app-primary-focus);
--app-group-box-shadow-focus-with-input: 0 0 0 0.0625rem
var(--app-form-element-border-color);
--app-modal-overlay-backdrop-filter: blur(0.375rem);
--app-nav-element-spacing-vertical: 1rem;
--app-nav-element-spacing-horizontal: 0.5rem;
--app-nav-link-spacing-vertical: 0.5rem;
--app-nav-link-spacing-horizontal: 0.5rem;
--app-nav-breadcrumb-divider: ">";
--app-primary-h-base: 180.75;
--app-primary-s-base: 37.38%;
--app-primary-l-base: 58.04%;
--app-secondary-h-base: 223;
--app-secondary-s-base: 19%;
--app-secondary-l-base: 45%;
2026-02-11 19:28:12 +01:00
--app-blockquote-background-color: var(
--app-form-element-active-background-color
);
--app-list-tabs-border: var(--app-border);
--app-list-tabs-active: var(--app-primary);
--app-list-tabs-muted: var(--app-muted-color);
--app-tabs-border: var(--app-border);
--app-tabs-active: var(--app-primary);
--app-tabs-muted: var(--app-muted-color);
--app-tooltip-bg: var(--app-card-background-color);
--app-tooltip-color: var(--app-color);
--app-tooltip-border: var(--app-border);
--app-tooltip-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
--app-tooltip-radius: 8px;
--app-tooltip-offset: 8px;
2026-02-04 23:31:53 +01:00
--app-badge-background-color: coral;
2026-03-12 16:47:53 +01:00
--app-empty-border-color: var(--app-muted-border-color);
--app-empty-bg: var(--app-card-background-color);
--app-empty-text: var(--app-color);
--app-empty-hint: var(--app-muted-color);
/* Layout: topbar height and z-index layers */
--app-topbar-height: calc(3rem + 1px); /* 3rem content + 1px border-bottom */
--z-topbar: 100;
--z-drawer: 95;
--z-drawer-backdrop: 90;
feat(responsive): mobile polish — topbar, drawer, footer, tabs, notification dropdown Unifies the breakpoint system and fixes cross-layer CSS bugs that kept the old mobile hide rules from ever taking effect. Breakpoints: - Document canonical xs/sm/md/lg/xl/2xl scale in variables.base.css - Replace 968px one-offs with 1024px (app-details, app-page-editor) - Make details tab-panel max-width responsive (min(70ch, 100%)) Topbar (< md): - Hide breadcrumb in components layer so cross-layer overrides actually win - Dock search icon alongside right-column icons via flex layout - Add xs (400px) tightening for hit-area preservation - Hide brand divider alongside hidden brand name - Switch hover states to theme-aware --app-dropdown-hover-background-color Mobile drawer: - Icon-bar now vertical like desktop (was horizontal top strip, effectively invisible) - Sidebar shifted 52px right so icon-bar and sidebar slide in as one 280px unit - Add breathing room above first nav item (20px top padding) List tabs: - Rewrite .app-list-tabs to horizontal scroll with hidden scrollbar, snap, and mask-image gradient fades; add initListTabs to scroll active into view - Register as runtime component in app-init.js List toolbar: - Stack wide controls (multi-select, search/text inputs) full-width below sm, keep compact controls at natural width Footer: - New dedicated app-footer.css with mobile-first stacking, border-top separator, theme-aware hover; remove redundant rules from app-shell.css Notification dropdown: - Width with min(360px, 100vw - 2rem) and min-width: 0 override global details > ul rule; switch to position: fixed below md so it hugs the viewport right edge instead of the non-rightmost bell button Content spacing: - Add padding-block-start to .app-main-content on mobile (was 0) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 23:50:59 +02:00
/*
* Responsive breakpoint system canonical values, align all @media rules to these.
* CSS custom props cannot be used inside @media queries, so these are documentation only.
* Use them verbatim in px. Do not introduce one-off breakpoints (640, 720, 900, 968, 1100, ...).
* --bp-xs: 400px tiny phones (iPhone SE, Pixel 4a portrait)
* --bp-sm: 576px large phones / phablets
* --bp-md: 768px tablets portrait, sidebar/drawer switch
* --bp-lg: 1024px tablets landscape / small laptops, two-column details activates
* --bp-xl: 1280px desktops
* --bp-2xl: 1536px large desktops
*/
/* Typography tokens defined in typography.tokens.css */
2026-02-04 23:31:53 +01:00
}
/* Icons */
:host,
:root {
--app-icon-checkbox: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");
--app-icon-minus: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'%3E%3C/line%3E%3C/svg%3E");
--app-icon-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
--app-icon-date: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E");
--app-icon-time: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpolyline points='12 6 12 12 16 14'%3E%3C/polyline%3E%3C/svg%3E");
--app-icon-search: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E");
--app-icon-close: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E");
--app-icon-loading: url("data:image/svg+xml,%3Csvg fill='none' height='24' width='24' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' %3E%3Cstyle%3E g %7B animation: rotate 2s linear infinite; transform-origin: center center; %7D circle %7B stroke-dasharray: 75,100; stroke-dashoffset: -5; animation: dash 1.5s ease-in-out infinite; stroke-linecap: round; %7D @keyframes rotate %7B 0%25 %7B transform: rotate(0deg); %7D 100%25 %7B transform: rotate(360deg); %7D %7D @keyframes dash %7B 0%25 %7B stroke-dasharray: 1,100; stroke-dashoffset: 0; %7D 50%25 %7B stroke-dasharray: 44.5,100; stroke-dashoffset: -17.5; %7D 100%25 %7B stroke-dasharray: 44.5,100; stroke-dashoffset: -62; %7D %7D %3C/style%3E%3Cg%3E%3Ccircle cx='12' cy='12' r='10' fill='none' stroke='rgb(136, 145, 164)' stroke-width='4' /%3E%3C/g%3E%3C/svg%3E");
}
2026-02-11 19:28:12 +01:00
:host(:not([data-theme])),
:root:not([data-theme]),
2026-02-04 23:31:53 +01:00
[data-theme="light"] {
color-scheme: light;
--app-background-color: #fff;
2026-03-12 09:41:18 +01:00
--app-sidebar-background-color: var(--app-background-color);
2026-02-04 23:31:53 +01:00
--app-color: #373c44;
--app-text-selection-color: rgba(2, 154, 232, 0.25);
--app-muted-color: #646b79;
--app-muted-border-color: rgb(231, 234, 239.5);
--app-preview-bg: #e8ebef;
feat(tenant): per-theme logos + file-upload + button UI polish Replace the single tenant avatar with a pair of theme-scoped brand logos. Render only the theme-matching <img> server-side and swap src on theme toggle via a JS hook — no reload, no double request, no CSS tricks. Tenant logos - TenantLogoService (ImageUploadTrait) with theme whitelist and per-theme storage storage/tenants/{uuid}/logo/{light|dark}/, SIZES 128/256/512 - Public serving endpoint auth/tenant-logo-file so login can show the logo pre-auth; matching authenticated admin preview endpoint - appTenantLogoUrl(?size, ?theme) with 4-step fallback cascade; PDF + mail always request 'light' - Admin tenant edit: avatar block replaced by "Tenant logos" details block inside the Master-data tab, two side-by-side slots via Pico .grid with the core app-file-upload partial - Policy rename ABILITY_ADMIN_TENANTS_AVATAR_VIEW -> LOGO_VIEW, action routes logo / logo-delete / logo-file with theme body/query param - API endpoint path kept (backward compat), internals on new service - CLI tenant:logo-migrate-avatars moves legacy avatar/ -> logo/light/ idempotently (--dry-run, --yes, --cleanup) - i18n "Tenant image" removed, 12 new keys synced across de/en File upload component - Full-width preview + filename/actions below (3D stack layout) - Fixed 16:9 aspect ratio with 1rem inner padding for consistent preview size across any logo aspect - Transparency checker pattern as background so black logos stay visible on dark mode and white logos on light mode - form="" + deleteFormId support so the partial works with barrier forms inside another form Buttons - width:100% dropped from button[type="submit"]; scoped back via .login-main for the auth-flow primary CTA - .outline base rule now tints background via color-mix of --app-color so secondary/primary/danger outlines all gain a subtle surface - .outline.secondary restyled Stripe-style in both themes: solid white chip with soft shadow in light, solid elevated dark chip with white text in dark; neutral border replaces role-colored border - .app-action-success/.app-action-danger outlines get color-mix bg + theme-aware outline-text tokens for stronger contrast - Filled .primary/.app-action-success/.app-action-danger get raised box-shadow (inset highlight + drop) — opt-in via class so chrome buttons stay flat - Dropped the legacy .secondary utility that was clobbering the custom-property cascade with a hardcoded muted color Theme swap - Logo img carries data-src-light + data-src-dark; theme-toggle JS swaps src when data-theme changes, keeping the topbar/login logo in sync without a page reload Quality gates: PHPUnit (2045), PHPStan L5, CS-Fixer, docs link/drift, codex skills sync — all green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 20:25:53 +02:00
--app-button-filled-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.2),
0 1px 2px rgba(0, 0, 0, 0.12);
--app-button-filled-shadow-hover:
inset 0 1px 0 rgba(255, 255, 255, 0.25),
0 2px 4px rgba(0, 0, 0, 0.18);
--app-button-neutral-bg: #ffffff;
--app-button-neutral-bg-hover: #f6f8fa;
--app-button-neutral-border: #e3e6eb;
--app-button-neutral-border-hover: #c5c9d1;
--app-button-neutral-color: #1a1f2a;
--app-button-neutral-color-hover: #0b0f18;
--app-button-neutral-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.6),
0 1px 2px rgba(17, 24, 39, 0.08);
--app-button-neutral-shadow-hover:
inset 0 1px 0 rgba(255, 255, 255, 0.8),
0 2px 4px rgba(17, 24, 39, 0.12);
2026-02-04 23:31:53 +01:00
--app-primary-h-light: 180.75;
--app-primary-s-light: 37.38%;
--app-primary-l-light: 58.04%;
--app-primary-h: var(--app-primary-h-light, var(--app-primary-h-base));
--app-primary-s: var(--app-primary-s-light, var(--app-primary-s-base));
--app-primary-l: var(--app-primary-l-light, var(--app-primary-l-base));
--app-primary-l-hover: -7%;
--app-primary-l-hover-bg: -4%;
--app-primary-l-focus: 16%;
--app-primary: hsl(
var(--app-primary-h) var(--app-primary-s) var(--app-primary-l)
);
--app-primary-background: var(--app-primary);
--app-primary-border: var(--app-primary-background);
--app-primary-underline: hsl(
var(--app-primary-h) var(--app-primary-s) var(--app-primary-l) / 0.5
);
--app-primary-hover: hsl(
var(--app-primary-h) var(--app-primary-s)
calc(var(--app-primary-l) + var(--app-primary-l-hover))
);
--app-primary-hover-background: hsl(
var(--app-primary-h) var(--app-primary-s)
calc(var(--app-primary-l) + var(--app-primary-l-hover-bg))
);
--app-primary-hover-border: var(--app-primary-hover-background);
--app-primary-hover-underline: var(--app-primary-hover);
--app-primary-focus: hsl(
var(--app-primary-h) var(--app-primary-s)
calc(var(--app-primary-l) + var(--app-primary-l-focus)) / 0.5
);
--app-primary-inverse: #fff;
--app-secondary-h-light: 223;
--app-secondary-s-light: 19%;
--app-secondary-l-light: 45%;
--app-secondary-h: var(--app-secondary-h-light, var(--app-secondary-h-base));
--app-secondary-s: var(--app-secondary-s-light, var(--app-secondary-s-base));
--app-secondary-l: var(--app-secondary-l-light, var(--app-secondary-l-base));
--app-secondary-l-hover: -6%;
--app-secondary-l-hover-bg: -6%;
--app-secondary-l-focus: 0%;
--app-secondary: hsl(
var(--app-secondary-h) var(--app-secondary-s) var(--app-secondary-l)
);
--app-secondary-background: hsl(
var(--app-secondary-h) var(--app-secondary-s)
calc(var(--app-secondary-l) - 4%)
);
--app-secondary-border: var(--app-secondary-background);
--app-secondary-underline: hsl(
var(--app-secondary-h) var(--app-secondary-s) var(--app-secondary-l) / 0.5
);
--app-secondary-hover: hsl(
var(--app-secondary-h) var(--app-secondary-s)
calc(var(--app-secondary-l) + var(--app-secondary-l-hover))
);
--app-secondary-hover-background: hsl(
var(--app-secondary-h) var(--app-secondary-s)
calc(var(--app-secondary-l) + var(--app-secondary-l-hover-bg))
);
--app-secondary-hover-border: var(--app-secondary-hover-background);
--app-secondary-hover-underline: var(--app-secondary-hover);
--app-secondary-focus: hsl(
var(--app-secondary-h) var(--app-secondary-s) var(--app-secondary-l) / 0.25
);
--app-secondary-inverse: #fff;
--app-contrast: #181c25;
--app-contrast-background: #181c25;
--app-contrast-border: var(--app-contrast-background);
--app-contrast-underline: rgba(24, 28, 37, 0.5);
--app-contrast-hover: #000;
--app-contrast-hover-background: #000;
--app-contrast-hover-border: var(--app-contrast-hover-background);
--app-contrast-hover-underline: var(--app-secondary-hover);
--app-contrast-focus: rgba(93, 107, 137, 0.25);
--app-contrast-inverse: #fff;
--app-box-shadow:
0.0145rem 0.029rem 0.174rem rgba(129, 145, 181, 0.01698),
0.0335rem 0.067rem 0.402rem rgba(129, 145, 181, 0.024),
0.0625rem 0.125rem 0.75rem rgba(129, 145, 181, 0.03),
0.1125rem 0.225rem 1.35rem rgba(129, 145, 181, 0.036),
0.2085rem 0.417rem 2.502rem rgba(129, 145, 181, 0.04302),
0.5rem 1rem 6rem rgba(129, 145, 181, 0.06),
0 0 0 0.0625rem rgba(129, 145, 181, 0.015);
--app-h1-color: #2d3138;
--app-h2-color: #373c44;
--app-h3-color: #424751;
--app-h4-color: #4d535e;
--app-h5-color: #5c6370;
--app-h6-color: #646b79;
--app-mark-background-color: rgb(252.5, 230.5, 191.5);
--app-mark-color: #0f1114;
--app-ins-color: rgb(28.5, 105.5, 84);
--app-del-color: rgb(136, 56.5, 53);
2026-02-11 19:28:12 +01:00
--badge-success-bg: #e6f6eb;
--badge-success-color: #1f6a3a;
--badge-success-border: #c9ead3;
--badge-danger-bg: #fdecec;
--badge-danger-color: #8f2c2c;
--badge-danger-border: #f6caca;
--badge-info-bg: #e9f0ff;
--badge-info-color: #264db3;
--badge-info-border: #cbd7ff;
--badge-warn-bg: #fff5e6;
--badge-warn-color: #9a5a00;
--badge-warn-border: #ffd9a6;
--badge-neutral-bg: #f2f4f7;
--badge-neutral-color: #4a5568;
--badge-neutral-border: #e1e6ee;
--app-tile-bg: var(--app-card-background-color);
--app-tile-border: var(--app-muted-border-color);
--app-tile-shadow: var(--app-box-shadow);
--app-tile-icon-bg: rgba(0, 0, 0, 0.04);
--app-tile-icon-color: var(--app-contrast);
--app-tile-count-color: var(--app-contrast);
--app-tile-label-color: var(--app-color);
--app-tile-link-color: var(--app-muted-color);
2026-02-04 23:31:53 +01:00
--app-blockquote-border-color: var(--app-muted-border-color);
--app-blockquote-footer-color: var(--app-muted-color);
--app-button-box-shadow: 0 0 0 rgba(0, 0, 0, 0);
--app-button-hover-box-shadow: 0 0 0 rgba(0, 0, 0, 0);
--app-table-border-color: var(--app-muted-border-color);
--app-table-row-stripped-background-color: rgba(111, 120, 135, 0.0375);
--app-code-background-color: rgb(243, 244.5, 246.75);
--app-code-color: #646b79;
--app-code-kbd-background-color: var(--app-color);
--app-code-kbd-color: var(--app-background-color);
--app-form-element-background-color: rgb(251, 251.5, 252.25);
--app-form-element-selected-background-color: #dfe3eb;
--app-form-element-border-color: #cfd5e2;
--app-border: var(--app-form-element-border-color);
--app-form-element-color: #23262c;
--app-form-element-placeholder-color: var(--app-muted-color);
--app-form-element-active-background-color: #fff;
--app-form-element-active-border-color: var(--app-primary-border);
--app-form-element-focus-color: var(--app-primary-border);
--app-form-element-disabled-opacity: 0.5;
--app-form-element-disabled-background-color: #f0f2f5;
2026-03-04 15:56:58 +01:00
--app-form-element-readonly-background-color: #f4f7fb;
--app-form-element-readonly-border-color: #d6deea;
--app-form-element-readonly-color: #2f3440;
--app-form-element-readonly-focus-color: rgba(124, 138, 164, 0.3);
2026-02-04 23:31:53 +01:00
--app-form-element-invalid-border-color: rgb(183.5, 105.5, 106.5);
--app-form-element-invalid-active-border-color: rgb(200.25, 79.25, 72.25);
--app-form-element-invalid-focus-color: var(
--app-form-element-invalid-active-border-color
);
--app-form-element-invalid-active-shadow: 0 0 0 var(--app-outline-width)
var(--app-form-element-invalid-focus-color);
2026-02-04 23:31:53 +01:00
--app-form-element-valid-border-color: rgb(76, 154.5, 137.5);
--app-form-element-valid-active-border-color: rgb(39, 152.75, 118.75);
--app-form-element-valid-focus-color: var(
--app-form-element-valid-active-border-color
);
--app-notice-success-color: var(--app-form-element-valid-border-color);
--app-notice-success-border-color: var(--app-form-element-valid-border-color);
--app-notice-success-background-color: rgba(76, 154.5, 137.5, 0.12);
--app-notice-warning-color: rgb(173, 113, 34);
--app-notice-warning-border-color: rgb(214, 145, 55);
--app-notice-warning-background-color: rgba(214, 145, 55, 0.12);
--app-notice-info-color: var(--app-secondary);
--app-notice-info-border-color: var(--app-secondary);
--app-notice-info-background-color: rgba(96.9, 110.5, 137.7, 0.12);
--app-notice-error-color: var(--app-form-element-invalid-border-color);
--app-notice-error-border-color: var(--app-form-element-invalid-border-color);
--app-notice-error-background-color: rgb(255 0 0 / 13%);
2026-03-12 16:47:53 +01:00
--app-action-success-background: #1f7a45;
--app-action-success-border: #1f7a45;
--app-action-success-color: #fff;
--app-action-success-hover-background: #176238;
--app-action-success-hover-border: #176238;
--app-action-success-focus-color: rgba(31, 122, 69, 0.45);
--app-action-success-outline-background: rgba(31, 122, 69, 0.14);
feat(tenant): per-theme logos + file-upload + button UI polish Replace the single tenant avatar with a pair of theme-scoped brand logos. Render only the theme-matching <img> server-side and swap src on theme toggle via a JS hook — no reload, no double request, no CSS tricks. Tenant logos - TenantLogoService (ImageUploadTrait) with theme whitelist and per-theme storage storage/tenants/{uuid}/logo/{light|dark}/, SIZES 128/256/512 - Public serving endpoint auth/tenant-logo-file so login can show the logo pre-auth; matching authenticated admin preview endpoint - appTenantLogoUrl(?size, ?theme) with 4-step fallback cascade; PDF + mail always request 'light' - Admin tenant edit: avatar block replaced by "Tenant logos" details block inside the Master-data tab, two side-by-side slots via Pico .grid with the core app-file-upload partial - Policy rename ABILITY_ADMIN_TENANTS_AVATAR_VIEW -> LOGO_VIEW, action routes logo / logo-delete / logo-file with theme body/query param - API endpoint path kept (backward compat), internals on new service - CLI tenant:logo-migrate-avatars moves legacy avatar/ -> logo/light/ idempotently (--dry-run, --yes, --cleanup) - i18n "Tenant image" removed, 12 new keys synced across de/en File upload component - Full-width preview + filename/actions below (3D stack layout) - Fixed 16:9 aspect ratio with 1rem inner padding for consistent preview size across any logo aspect - Transparency checker pattern as background so black logos stay visible on dark mode and white logos on light mode - form="" + deleteFormId support so the partial works with barrier forms inside another form Buttons - width:100% dropped from button[type="submit"]; scoped back via .login-main for the auth-flow primary CTA - .outline base rule now tints background via color-mix of --app-color so secondary/primary/danger outlines all gain a subtle surface - .outline.secondary restyled Stripe-style in both themes: solid white chip with soft shadow in light, solid elevated dark chip with white text in dark; neutral border replaces role-colored border - .app-action-success/.app-action-danger outlines get color-mix bg + theme-aware outline-text tokens for stronger contrast - Filled .primary/.app-action-success/.app-action-danger get raised box-shadow (inset highlight + drop) — opt-in via class so chrome buttons stay flat - Dropped the legacy .secondary utility that was clobbering the custom-property cascade with a hardcoded muted color Theme swap - Logo img carries data-src-light + data-src-dark; theme-toggle JS swaps src when data-theme changes, keeping the topbar/login logo in sync without a page reload Quality gates: PHPUnit (2045), PHPStan L5, CS-Fixer, docs link/drift, codex skills sync — all green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 20:25:53 +02:00
--app-action-success-outline-color: #155a32;
2026-03-12 16:47:53 +01:00
--app-action-danger-background: #b33636;
--app-action-danger-border: #b33636;
--app-action-danger-color: #fff;
--app-action-danger-hover-background: #952d2d;
--app-action-danger-hover-border: #952d2d;
--app-action-danger-focus-color: rgba(179, 54, 54, 0.45);
--app-action-danger-outline-background: rgba(179, 54, 54, 0.14);
feat(tenant): per-theme logos + file-upload + button UI polish Replace the single tenant avatar with a pair of theme-scoped brand logos. Render only the theme-matching <img> server-side and swap src on theme toggle via a JS hook — no reload, no double request, no CSS tricks. Tenant logos - TenantLogoService (ImageUploadTrait) with theme whitelist and per-theme storage storage/tenants/{uuid}/logo/{light|dark}/, SIZES 128/256/512 - Public serving endpoint auth/tenant-logo-file so login can show the logo pre-auth; matching authenticated admin preview endpoint - appTenantLogoUrl(?size, ?theme) with 4-step fallback cascade; PDF + mail always request 'light' - Admin tenant edit: avatar block replaced by "Tenant logos" details block inside the Master-data tab, two side-by-side slots via Pico .grid with the core app-file-upload partial - Policy rename ABILITY_ADMIN_TENANTS_AVATAR_VIEW -> LOGO_VIEW, action routes logo / logo-delete / logo-file with theme body/query param - API endpoint path kept (backward compat), internals on new service - CLI tenant:logo-migrate-avatars moves legacy avatar/ -> logo/light/ idempotently (--dry-run, --yes, --cleanup) - i18n "Tenant image" removed, 12 new keys synced across de/en File upload component - Full-width preview + filename/actions below (3D stack layout) - Fixed 16:9 aspect ratio with 1rem inner padding for consistent preview size across any logo aspect - Transparency checker pattern as background so black logos stay visible on dark mode and white logos on light mode - form="" + deleteFormId support so the partial works with barrier forms inside another form Buttons - width:100% dropped from button[type="submit"]; scoped back via .login-main for the auth-flow primary CTA - .outline base rule now tints background via color-mix of --app-color so secondary/primary/danger outlines all gain a subtle surface - .outline.secondary restyled Stripe-style in both themes: solid white chip with soft shadow in light, solid elevated dark chip with white text in dark; neutral border replaces role-colored border - .app-action-success/.app-action-danger outlines get color-mix bg + theme-aware outline-text tokens for stronger contrast - Filled .primary/.app-action-success/.app-action-danger get raised box-shadow (inset highlight + drop) — opt-in via class so chrome buttons stay flat - Dropped the legacy .secondary utility that was clobbering the custom-property cascade with a hardcoded muted color Theme swap - Logo img carries data-src-light + data-src-dark; theme-toggle JS swaps src when data-theme changes, keeping the topbar/login logo in sync without a page reload Quality gates: PHPUnit (2045), PHPStan L5, CS-Fixer, docs link/drift, codex skills sync — all green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 20:25:53 +02:00
--app-action-danger-outline-color: #8f2727;
2026-02-04 23:31:53 +01:00
--app-switch-background-color: #bfc7d9;
--app-switch-checked-background-color: var(--app-primary-background);
--app-switch-color: #fff;
--app-switch-thumb-box-shadow: 0 0 0 rgba(0, 0, 0, 0);
--app-range-border-color: #dfe3eb;
--app-range-active-border-color: #bfc7d9;
--app-range-thumb-border-color: var(--app-background-color);
--app-range-thumb-color: var(--app-secondary-background);
--app-range-thumb-active-color: var(--app-primary-background);
--app-accordion-border-color: var(--app-muted-border-color);
--app-accordion-active-summary-color: var(--app-primary-hover);
--app-accordion-close-summary-color: var(--app-color);
--app-accordion-open-summary-color: var(--app-muted-color);
--app-card-background-color: var(--app-background-color);
--app-card-border-color: var(--app-muted-border-color);
--app-card-box-shadow: var(--app-box-shadow);
--app-card-sectioning-background-color: rgb(251, 251.5, 252.25);
2026-03-09 21:57:52 +01:00
--app-details-card-open-summary-bg: var(--app-card-sectioning-background-color);
2026-02-04 23:31:53 +01:00
--app-dropdown-background-color: #fff;
--app-dropdown-border-color: #eff1f4;
--app-dropdown-box-shadow: var(--app-box-shadow);
--app-dropdown-color: var(--app-color);
--app-dropdown-hover-background-color: #eff1f4;
--app-loading-spinner-opacity: 0.5;
--app-modal-overlay-background-color: rgba(232, 234, 237, 0.75);
--app-progress-background-color: #dfe3eb;
--app-progress-color: var(--app-primary-background);
--app-icon-valid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(76, 154.5, 137.5)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");
--app-icon-invalid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(200.25, 79.25, 72.25)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E");
}
2026-02-11 19:28:12 +01:00
:host(:not([data-theme]))
2026-02-04 23:31:53 +01:00
input:is(
[type="submit"],
[type="button"],
[type="reset"],
[type="checkbox"],
[type="radio"],
[type="file"]
),
2026-02-11 19:28:12 +01:00
:root:not([data-theme])
2026-02-04 23:31:53 +01:00
input:is(
[type="submit"],
[type="button"],
[type="reset"],
[type="checkbox"],
[type="radio"],
[type="file"]
),
[data-theme="light"]
input:is(
[type="submit"],
[type="button"],
[type="reset"],
[type="checkbox"],
[type="radio"],
[type="file"]
) {
--app-form-element-focus-color: var(--app-primary-focus);
}
@media only screen and (prefers-color-scheme: dark) {
:host(:not([data-theme])),
:root:not([data-theme]) {
color-scheme: dark;
--app-background-color: #1f1f1e;
--app-sidebar-background-color: #171716;
--app-color: #c8c8c6;
2026-02-04 23:31:53 +01:00
--app-text-selection-color: rgba(1, 170, 255, 0.1875);
--app-muted-color: #6e6e6c;
--app-muted-border-color: #2f2f2d;
2026-02-04 23:31:53 +01:00
--app-primary-h-dark: var(--app-primary-h-light, var(--app-primary-h-base));
--app-primary-s-dark: var(--app-primary-s-light, var(--app-primary-s-base));
--app-primary-l-dark: var(--app-primary-l-light, var(--app-primary-l-base));
--app-primary-h: var(--app-primary-h-dark, var(--app-primary-h-base));
--app-primary-s: var(--app-primary-s-dark, var(--app-primary-s-base));
--app-primary-l: var(--app-primary-l-dark, var(--app-primary-l-base));
--app-primary-l-hover: -7%;
--app-primary-l-hover-bg: -4%;
--app-primary-l-focus: 16%;
--app-primary: hsl(
var(--app-primary-h) var(--app-primary-s) var(--app-primary-l)
);
--app-primary-background: var(--app-primary);
--app-primary-border: var(--app-primary-background);
--app-primary-underline: hsl(
var(--app-primary-h) var(--app-primary-s) var(--app-primary-l) / 0.5
);
--app-primary-hover: hsl(
var(--app-primary-h) var(--app-primary-s)
calc(var(--app-primary-l) + var(--app-primary-l-hover))
);
--app-primary-hover-background: hsl(
var(--app-primary-h) var(--app-primary-s)
calc(var(--app-primary-l) + var(--app-primary-l-hover-bg))
);
--app-primary-hover-border: var(--app-primary-hover-background);
--app-primary-hover-underline: var(--app-primary-hover);
--app-primary-focus: hsl(
var(--app-primary-h) var(--app-primary-s)
calc(var(--app-primary-l) + var(--app-primary-l-focus)) / 0.5
);
--app-primary-inverse: #fff;
--app-secondary-h-dark: var(
--app-secondary-h-light,
var(--app-secondary-h-base)
);
--app-secondary-s-dark: var(
--app-secondary-s-light,
var(--app-secondary-s-base)
);
--app-secondary-l-dark: var(
--app-secondary-l-light,
var(--app-secondary-l-base)
);
--app-secondary-h: var(--app-secondary-h-dark, var(--app-secondary-h-base));
--app-secondary-s: var(--app-secondary-s-dark, var(--app-secondary-s-base));
--app-secondary-l: var(--app-secondary-l-dark, var(--app-secondary-l-base));
--app-secondary-l-hover: -6%;
--app-secondary-l-hover-bg: -6%;
--app-secondary-l-focus: 0%;
--app-secondary: hsl(
var(--app-secondary-h) var(--app-secondary-s) var(--app-secondary-l)
);
--app-secondary-background: hsl(
var(--app-secondary-h) var(--app-secondary-s)
calc(var(--app-secondary-l) - 4%)
);
--app-secondary-border: var(--app-secondary-background);
--app-secondary-underline: hsl(
var(--app-secondary-h) var(--app-secondary-s) var(--app-secondary-l) / 0.5
);
--app-secondary-hover: hsl(
var(--app-secondary-h) var(--app-secondary-s)
calc(var(--app-secondary-l) + var(--app-secondary-l-hover))
);
--app-secondary-hover-background: hsl(
var(--app-secondary-h) var(--app-secondary-s)
calc(var(--app-secondary-l) + var(--app-secondary-l-hover-bg))
);
--app-secondary-hover-border: var(--app-secondary-hover-background);
--app-secondary-hover-underline: var(--app-secondary-hover);
--app-secondary-focus: hsl(
var(--app-secondary-h) var(--app-secondary-s) var(--app-secondary-l) /
0.25
);
--app-secondary-inverse: #fff;
--app-contrast: #dfe3eb;
--app-contrast-background: #eff1f4;
--app-contrast-border: var(--app-contrast-background);
--app-contrast-underline: rgba(223, 227, 235, 0.5);
--app-contrast-hover: #fff;
--app-contrast-hover-background: #fff;
--app-contrast-hover-border: var(--app-contrast-hover-background);
--app-contrast-hover-underline: var(--app-contrast-hover);
--app-contrast-focus: rgba(207, 213, 226, 0.25);
--app-contrast-inverse: #000;
--app-box-shadow:
0.0145rem 0.029rem 0.174rem rgba(0, 0, 0, 0.01698),
0.0335rem 0.067rem 0.402rem rgba(0, 0, 0, 0.024),
0.0625rem 0.125rem 0.75rem rgba(0, 0, 0, 0.03),
0.1125rem 0.225rem 1.35rem rgba(0, 0, 0, 0.036),
0.2085rem 0.417rem 2.502rem rgba(0, 0, 0, 0.04302),
0.5rem 1rem 6rem rgba(0, 0, 0, 0.06),
0 0 0 0.0625rem rgba(0, 0, 0, 0.015);
--app-h1-color: #f0f0ee;
--app-h2-color: #e0e0de;
--app-h3-color: #c8c8c6;
--app-h4-color: #b3b3b1;
--app-h5-color: #a4a4a2;
--app-h6-color: #888886;
--app-mark-background-color: #1a3a4a;
2026-02-04 23:31:53 +01:00
--app-mark-color: #fff;
--app-ins-color: #62af9a;
--app-del-color: rgb(205.5, 126, 123);
2026-02-11 19:28:12 +01:00
--badge-success-bg: #163a2a;
--badge-success-color: #8fe0b0;
--badge-success-border: #25523c;
--badge-danger-bg: #3a1b1b;
--badge-danger-color: #f2a3a3;
--badge-danger-border: #5b2c2c;
--badge-info-bg: #1a253f;
--badge-info-color: #9fb6ff;
--badge-info-border: #2a3a66;
--badge-warn-bg: #3a2a14;
--badge-warn-color: #ffd596;
--badge-warn-border: #5b401d;
--badge-neutral-bg: #222831;
--badge-neutral-color: #cbd5e1;
--badge-neutral-border: #2f3945;
--app-tile-bg: rgba(23, 23, 22, 0.6);
--app-tile-border: rgba(255, 255, 255, 0.06);
2026-02-11 19:28:12 +01:00
--app-tile-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
--app-tile-icon-bg: rgba(255, 255, 255, 0.06);
--app-tile-icon-color: #e8e8e6;
--app-tile-count-color: #f2f2f0;
--app-tile-label-color: #d4d4d2;
--app-tile-link-color: #9e9e9c;
2026-02-04 23:31:53 +01:00
--app-blockquote-border-color: var(--app-muted-border-color);
--app-blockquote-footer-color: var(--app-muted-color);
--app-button-box-shadow: 0 0 0 rgba(0, 0, 0, 0);
--app-button-hover-box-shadow: 0 0 0 rgba(0, 0, 0, 0);
--app-table-border-color: var(--app-muted-border-color);
--app-table-row-stripped-background-color: rgba(111, 120, 135, 0.0375);
--app-code-background-color: #2a2a28;
--app-code-color: #888886;
2026-02-04 23:31:53 +01:00
--app-code-kbd-background-color: var(--app-color);
--app-code-kbd-color: var(--app-background-color);
--app-form-element-background-color: #2a2a28;
--app-form-element-selected-background-color: #383835;
--app-form-element-border-color: #383835;
2026-02-04 23:31:53 +01:00
--app-border: var(--app-form-element-border-color);
--app-form-element-color: #e0e0de;
--app-form-element-placeholder-color: #7a7a78;
--app-form-element-active-background-color: #2a2a28;
2026-02-04 23:31:53 +01:00
--app-form-element-active-border-color: var(--app-primary-border);
--app-form-element-focus-color: var(--app-primary-border);
--app-form-element-disabled-opacity: 0.5;
--app-form-element-disabled-background-color: #1a1a18;
--app-form-element-readonly-background-color: #333330;
--app-form-element-readonly-border-color: #4a4a48;
--app-form-element-readonly-color: #d7d7d5;
--app-form-element-readonly-focus-color: rgba(130, 130, 128, 0.35);
2026-02-04 23:31:53 +01:00
--app-form-element-invalid-border-color: rgb(149.5, 74, 80);
--app-form-element-invalid-active-border-color: rgb(183.25, 63.5, 59);
--app-form-element-invalid-focus-color: var(
--app-form-element-invalid-active-border-color
);
--app-form-element-invalid-active-shadow: 0 0 0 var(--app-outline-width)
var(--app-form-element-invalid-focus-color);
2026-02-04 23:31:53 +01:00
--app-form-element-valid-border-color: #2a7b6f;
--app-form-element-valid-active-border-color: rgb(22, 137, 105.5);
--app-form-element-valid-focus-color: var(
--app-form-element-valid-active-border-color
);
2026-03-12 16:47:53 +01:00
--app-action-success-background: #1f7a45;
--app-action-success-border: #1f7a45;
--app-action-success-color: #fff;
--app-action-success-hover-background: #176238;
--app-action-success-hover-border: #176238;
--app-action-success-focus-color: rgba(31, 122, 69, 0.5);
--app-action-success-outline-background: rgba(31, 122, 69, 0.2);
--app-action-danger-background: #b33636;
--app-action-danger-border: #b33636;
--app-action-danger-color: #fff;
--app-action-danger-hover-background: #952d2d;
--app-action-danger-hover-border: #952d2d;
--app-action-danger-focus-color: rgba(179, 54, 54, 0.5);
--app-action-danger-outline-background: rgba(179, 54, 54, 0.2);
--app-switch-background-color: #3a3a38;
2026-02-04 23:31:53 +01:00
--app-switch-checked-background-color: var(--app-primary-background);
--app-switch-color: #fff;
--app-switch-thumb-box-shadow: 0 0 0 rgba(0, 0, 0, 0);
--app-range-border-color: #2f2f2d;
--app-range-active-border-color: #383835;
2026-02-04 23:31:53 +01:00
--app-range-thumb-border-color: var(--app-background-color);
--app-range-thumb-color: var(--app-secondary-background);
--app-range-thumb-active-color: var(--app-primary-background);
--app-accordion-border-color: var(--app-muted-border-color);
--app-accordion-active-summary-color: var(--app-primary-hover);
--app-accordion-close-summary-color: var(--app-color);
--app-accordion-open-summary-color: var(--app-muted-color);
--app-card-background-color: #242422;
2026-02-04 23:31:53 +01:00
--app-card-border-color: var(--app-card-background-color);
--app-card-box-shadow: var(--app-box-shadow);
--app-card-sectioning-background-color: #2a2a28;
2026-03-09 21:57:52 +01:00
--app-details-card-open-summary-bg: var(--app-card-sectioning-background-color);
--app-dropdown-background-color: #242422;
--app-dropdown-border-color: #2f2f2d;
2026-02-04 23:31:53 +01:00
--app-dropdown-box-shadow: var(--app-box-shadow);
--app-dropdown-color: var(--app-color);
--app-dropdown-hover-background-color: #2f2f2d;
2026-02-04 23:31:53 +01:00
--app-loading-spinner-opacity: 0.5;
--app-modal-overlay-background-color: rgba(15, 15, 14, 0.75);
--app-progress-background-color: #2f2f2d;
2026-02-04 23:31:53 +01:00
--app-progress-color: var(--app-primary-background);
--app-icon-valid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(42, 123, 111)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");
--app-icon-invalid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(149.5, 74, 80)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E");
}
:host(:not([data-theme]))
input:is(
[type="submit"],
[type="button"],
[type="reset"],
[type="checkbox"],
[type="radio"],
[type="file"]
),
:root:not([data-theme])
input:is(
[type="submit"],
[type="button"],
[type="reset"],
[type="checkbox"],
[type="radio"],
[type="file"]
) {
--app-form-element-focus-color: var(--app-primary-focus);
}
}
[data-theme="dark"] {
2026-02-04 23:31:53 +01:00
color-scheme: dark;
--app-background-color: #1f1f1e;
--app-sidebar-background-color: #171716;
--app-color: #c8c8c6;
2026-02-04 23:31:53 +01:00
--app-text-selection-color: rgba(1, 170, 255, 0.1875);
--app-muted-color: #6e6e6c;
--app-muted-border-color: #2f2f2d;
--app-preview-bg: #262a2f;
feat(tenant): per-theme logos + file-upload + button UI polish Replace the single tenant avatar with a pair of theme-scoped brand logos. Render only the theme-matching <img> server-side and swap src on theme toggle via a JS hook — no reload, no double request, no CSS tricks. Tenant logos - TenantLogoService (ImageUploadTrait) with theme whitelist and per-theme storage storage/tenants/{uuid}/logo/{light|dark}/, SIZES 128/256/512 - Public serving endpoint auth/tenant-logo-file so login can show the logo pre-auth; matching authenticated admin preview endpoint - appTenantLogoUrl(?size, ?theme) with 4-step fallback cascade; PDF + mail always request 'light' - Admin tenant edit: avatar block replaced by "Tenant logos" details block inside the Master-data tab, two side-by-side slots via Pico .grid with the core app-file-upload partial - Policy rename ABILITY_ADMIN_TENANTS_AVATAR_VIEW -> LOGO_VIEW, action routes logo / logo-delete / logo-file with theme body/query param - API endpoint path kept (backward compat), internals on new service - CLI tenant:logo-migrate-avatars moves legacy avatar/ -> logo/light/ idempotently (--dry-run, --yes, --cleanup) - i18n "Tenant image" removed, 12 new keys synced across de/en File upload component - Full-width preview + filename/actions below (3D stack layout) - Fixed 16:9 aspect ratio with 1rem inner padding for consistent preview size across any logo aspect - Transparency checker pattern as background so black logos stay visible on dark mode and white logos on light mode - form="" + deleteFormId support so the partial works with barrier forms inside another form Buttons - width:100% dropped from button[type="submit"]; scoped back via .login-main for the auth-flow primary CTA - .outline base rule now tints background via color-mix of --app-color so secondary/primary/danger outlines all gain a subtle surface - .outline.secondary restyled Stripe-style in both themes: solid white chip with soft shadow in light, solid elevated dark chip with white text in dark; neutral border replaces role-colored border - .app-action-success/.app-action-danger outlines get color-mix bg + theme-aware outline-text tokens for stronger contrast - Filled .primary/.app-action-success/.app-action-danger get raised box-shadow (inset highlight + drop) — opt-in via class so chrome buttons stay flat - Dropped the legacy .secondary utility that was clobbering the custom-property cascade with a hardcoded muted color Theme swap - Logo img carries data-src-light + data-src-dark; theme-toggle JS swaps src when data-theme changes, keeping the topbar/login logo in sync without a page reload Quality gates: PHPUnit (2045), PHPStan L5, CS-Fixer, docs link/drift, codex skills sync — all green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 20:25:53 +02:00
--app-button-neutral-bg: #30343b;
--app-button-neutral-bg-hover: #3b4048;
--app-button-neutral-border: #464b54;
--app-button-neutral-border-hover: #5b616c;
--app-button-neutral-color: #d5d9e0;
--app-button-neutral-color-hover: #ffffff;
--app-button-neutral-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.06),
0 1px 2px rgba(0, 0, 0, 0.3);
--app-button-neutral-shadow-hover:
inset 0 1px 0 rgba(255, 255, 255, 0.1),
0 2px 5px rgba(0, 0, 0, 0.35);
--app-button-filled-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.15),
0 1px 2px rgba(0, 0, 0, 0.4);
--app-button-filled-shadow-hover:
inset 0 1px 0 rgba(255, 255, 255, 0.2),
0 2px 5px rgba(0, 0, 0, 0.5);
2026-02-04 23:31:53 +01:00
--app-primary-h-dark: var(--app-primary-h-light, var(--app-primary-h-base));
--app-primary-s-dark: var(--app-primary-s-light, var(--app-primary-s-base));
--app-primary-l-dark: var(--app-primary-l-light, var(--app-primary-l-base));
--app-primary-h: var(--app-primary-h-dark, var(--app-primary-h-base));
--app-primary-s: var(--app-primary-s-dark, var(--app-primary-s-base));
--app-primary-l: var(--app-primary-l-dark, var(--app-primary-l-base));
--app-primary-l-hover: -7%;
--app-primary-l-hover-bg: -4%;
--app-primary-l-focus: 16%;
--app-primary: hsl(
var(--app-primary-h) var(--app-primary-s) var(--app-primary-l)
);
--app-primary-background: var(--app-primary);
--app-primary-border: var(--app-primary-background);
--app-primary-underline: hsl(
var(--app-primary-h) var(--app-primary-s) var(--app-primary-l) / 0.5
);
--app-primary-hover: hsl(
var(--app-primary-h) var(--app-primary-s)
calc(var(--app-primary-l) + var(--app-primary-l-hover))
);
--app-primary-hover-background: hsl(
var(--app-primary-h) var(--app-primary-s)
calc(var(--app-primary-l) + var(--app-primary-l-hover-bg))
);
--app-primary-hover-border: var(--app-primary-hover-background);
--app-primary-hover-underline: var(--app-primary-hover);
--app-primary-focus: hsl(
var(--app-primary-h) var(--app-primary-s)
calc(var(--app-primary-l) + var(--app-primary-l-focus)) / 0.5
);
--app-primary-inverse: #fff;
--app-secondary-h-dark: var(
--app-secondary-h-light,
var(--app-secondary-h-base)
);
--app-secondary-s-dark: var(
--app-secondary-s-light,
var(--app-secondary-s-base)
);
--app-secondary-l-dark: var(
--app-secondary-l-light,
var(--app-secondary-l-base)
);
--app-secondary-h: var(--app-secondary-h-dark, var(--app-secondary-h-base));
--app-secondary-s: var(--app-secondary-s-dark, var(--app-secondary-s-base));
--app-secondary-l: var(--app-secondary-l-dark, var(--app-secondary-l-base));
--app-secondary-l-hover: -6%;
--app-secondary-l-hover-bg: -6%;
--app-secondary-l-focus: 0%;
--app-secondary: hsl(
var(--app-secondary-h) var(--app-secondary-s) var(--app-secondary-l)
);
--app-secondary-background: hsl(
var(--app-secondary-h) var(--app-secondary-s)
calc(var(--app-secondary-l) - 4%)
);
--app-secondary-border: var(--app-secondary-background);
--app-secondary-underline: hsl(
var(--app-secondary-h) var(--app-secondary-s) var(--app-secondary-l) / 0.5
);
--app-secondary-hover: hsl(
var(--app-secondary-h) var(--app-secondary-s)
calc(var(--app-secondary-l) + var(--app-secondary-l-hover))
);
--app-secondary-hover-background: hsl(
var(--app-secondary-h) var(--app-secondary-s)
calc(var(--app-secondary-l) + var(--app-secondary-l-hover-bg))
);
--app-secondary-hover-border: var(--app-secondary-hover-background);
--app-secondary-hover-underline: var(--app-secondary-hover);
--app-secondary-focus: hsl(
var(--app-secondary-h) var(--app-secondary-s) var(--app-secondary-l) / 0.25
);
--app-secondary-inverse: #fff;
--app-contrast: #dfe3eb;
--app-contrast-background: #eff1f4;
--app-contrast-border: var(--app-contrast-background);
--app-contrast-underline: rgba(223, 227, 235, 0.5);
--app-contrast-hover: #fff;
--app-contrast-hover-background: #fff;
--app-contrast-hover-border: var(--app-contrast-hover-background);
--app-contrast-hover-underline: var(--app-contrast-hover);
--app-contrast-focus: rgba(207, 213, 226, 0.25);
--app-contrast-inverse: #000;
--app-box-shadow:
0.0145rem 0.029rem 0.174rem rgba(0, 0, 0, 0.01698),
0.0335rem 0.067rem 0.402rem rgba(0, 0, 0, 0.024),
0.0625rem 0.125rem 0.75rem rgba(0, 0, 0, 0.03),
0.1125rem 0.225rem 1.35rem rgba(0, 0, 0, 0.036),
0.2085rem 0.417rem 2.502rem rgba(0, 0, 0, 0.04302),
0.5rem 1rem 6rem rgba(0, 0, 0, 0.06),
0 0 0 0.0625rem rgba(0, 0, 0, 0.015);
--app-h1-color: #f0f0ee;
--app-h2-color: #e0e0de;
--app-h3-color: #c8c8c6;
--app-h4-color: #b3b3b1;
--app-h5-color: #a4a4a2;
--app-h6-color: #888886;
--app-mark-background-color: #1a3a4a;
2026-02-04 23:31:53 +01:00
--app-mark-color: #fff;
--app-ins-color: #62af9a;
--app-del-color: rgb(205.5, 126, 123);
2026-02-11 19:28:12 +01:00
--badge-success-bg: #163a2a;
--badge-success-color: #8fe0b0;
--badge-success-border: #25523c;
--badge-danger-bg: #3a1b1b;
--badge-danger-color: #f2a3a3;
--badge-danger-border: #5b2c2c;
--badge-info-bg: #1a253f;
--badge-info-color: #9fb6ff;
--badge-info-border: #2a3a66;
--badge-warn-bg: #3a2a14;
--badge-warn-color: #ffd596;
--badge-warn-border: #5b401d;
--badge-neutral-bg: #222831;
--badge-neutral-color: #cbd5e1;
--badge-neutral-border: #2f3945;
--app-tile-bg: rgba(23, 23, 22, 0.6);
--app-tile-border: rgba(255, 255, 255, 0.06);
2026-02-11 19:28:12 +01:00
--app-tile-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
--app-tile-icon-bg: rgba(255, 255, 255, 0.06);
--app-tile-icon-color: #e8e8e6;
--app-tile-count-color: #f2f2f0;
--app-tile-label-color: #d4d4d2;
--app-tile-link-color: #9e9e9c;
2026-02-04 23:31:53 +01:00
--app-blockquote-border-color: var(--app-muted-border-color);
--app-blockquote-footer-color: var(--app-muted-color);
--app-button-box-shadow: 0 0 0 rgba(0, 0, 0, 0);
--app-button-hover-box-shadow: 0 0 0 rgba(0, 0, 0, 0);
--app-table-border-color: var(--app-muted-border-color);
--app-table-row-stripped-background-color: rgba(111, 120, 135, 0.0375);
--app-code-background-color: #2a2a28;
--app-code-color: #888886;
2026-02-04 23:31:53 +01:00
--app-code-kbd-background-color: var(--app-color);
--app-code-kbd-color: var(--app-background-color);
--app-form-element-background-color: #2a2a28;
--app-form-element-selected-background-color: #383835;
--app-form-element-border-color: #383835;
2026-02-04 23:31:53 +01:00
--app-border: var(--app-form-element-border-color);
--app-form-element-color: #e0e0de;
--app-form-element-placeholder-color: #7a7a78;
--app-form-element-active-background-color: #2a2a28;
2026-02-04 23:31:53 +01:00
--app-form-element-active-border-color: var(--app-primary-border);
--app-form-element-focus-color: var(--app-primary-border);
--app-form-element-disabled-opacity: 0.5;
--app-form-element-disabled-background-color: #1a1a18;
--app-form-element-readonly-background-color: #333330;
--app-form-element-readonly-border-color: #4a4a48;
--app-form-element-readonly-color: #d7d7d5;
--app-form-element-readonly-focus-color: rgba(130, 130, 128, 0.35);
2026-02-04 23:31:53 +01:00
--app-form-element-invalid-border-color: rgb(149.5, 74, 80);
--app-form-element-invalid-active-border-color: rgb(183.25, 63.5, 59);
--app-form-element-invalid-focus-color: var(
--app-form-element-invalid-active-border-color
);
--app-form-element-invalid-active-shadow: 0 0 0 var(--app-outline-width)
var(--app-form-element-invalid-focus-color);
2026-02-04 23:31:53 +01:00
--app-form-element-valid-border-color: #2a7b6f;
--app-form-element-valid-active-border-color: rgb(22, 137, 105.5);
--app-form-element-valid-focus-color: var(
--app-form-element-valid-active-border-color
);
--app-notice-success-color: var(--app-form-element-valid-border-color);
--app-notice-success-border-color: var(--app-form-element-valid-border-color);
--app-notice-success-background-color: rgba(42, 123, 111, 0.2);
--app-notice-warning-color: rgb(199, 140, 61);
--app-notice-warning-border-color: rgb(199, 140, 61);
--app-notice-warning-background-color: rgba(199, 140, 61, 0.2);
--app-notice-info-color: var(--app-secondary);
--app-notice-info-border-color: var(--app-secondary);
--app-notice-info-background-color: rgba(114.4, 130.1, 165.1, 0.2);
--app-notice-error-color: var(--app-form-element-invalid-border-color);
--app-notice-error-border-color: var(--app-form-element-invalid-border-color);
--app-notice-error-background-color: rgba(149.5, 74, 80, 0.2);
2026-03-12 16:47:53 +01:00
--app-action-success-background: #1f7a45;
--app-action-success-border: #1f7a45;
--app-action-success-color: #fff;
--app-action-success-hover-background: #176238;
--app-action-success-hover-border: #176238;
--app-action-success-focus-color: rgba(31, 122, 69, 0.5);
--app-action-success-outline-background: rgba(31, 122, 69, 0.2);
feat(tenant): per-theme logos + file-upload + button UI polish Replace the single tenant avatar with a pair of theme-scoped brand logos. Render only the theme-matching <img> server-side and swap src on theme toggle via a JS hook — no reload, no double request, no CSS tricks. Tenant logos - TenantLogoService (ImageUploadTrait) with theme whitelist and per-theme storage storage/tenants/{uuid}/logo/{light|dark}/, SIZES 128/256/512 - Public serving endpoint auth/tenant-logo-file so login can show the logo pre-auth; matching authenticated admin preview endpoint - appTenantLogoUrl(?size, ?theme) with 4-step fallback cascade; PDF + mail always request 'light' - Admin tenant edit: avatar block replaced by "Tenant logos" details block inside the Master-data tab, two side-by-side slots via Pico .grid with the core app-file-upload partial - Policy rename ABILITY_ADMIN_TENANTS_AVATAR_VIEW -> LOGO_VIEW, action routes logo / logo-delete / logo-file with theme body/query param - API endpoint path kept (backward compat), internals on new service - CLI tenant:logo-migrate-avatars moves legacy avatar/ -> logo/light/ idempotently (--dry-run, --yes, --cleanup) - i18n "Tenant image" removed, 12 new keys synced across de/en File upload component - Full-width preview + filename/actions below (3D stack layout) - Fixed 16:9 aspect ratio with 1rem inner padding for consistent preview size across any logo aspect - Transparency checker pattern as background so black logos stay visible on dark mode and white logos on light mode - form="" + deleteFormId support so the partial works with barrier forms inside another form Buttons - width:100% dropped from button[type="submit"]; scoped back via .login-main for the auth-flow primary CTA - .outline base rule now tints background via color-mix of --app-color so secondary/primary/danger outlines all gain a subtle surface - .outline.secondary restyled Stripe-style in both themes: solid white chip with soft shadow in light, solid elevated dark chip with white text in dark; neutral border replaces role-colored border - .app-action-success/.app-action-danger outlines get color-mix bg + theme-aware outline-text tokens for stronger contrast - Filled .primary/.app-action-success/.app-action-danger get raised box-shadow (inset highlight + drop) — opt-in via class so chrome buttons stay flat - Dropped the legacy .secondary utility that was clobbering the custom-property cascade with a hardcoded muted color Theme swap - Logo img carries data-src-light + data-src-dark; theme-toggle JS swaps src when data-theme changes, keeping the topbar/login logo in sync without a page reload Quality gates: PHPUnit (2045), PHPStan L5, CS-Fixer, docs link/drift, codex skills sync — all green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 20:25:53 +02:00
--app-action-success-outline-color: #4cc37a;
2026-03-12 16:47:53 +01:00
--app-action-danger-background: #b33636;
--app-action-danger-border: #b33636;
--app-action-danger-color: #fff;
--app-action-danger-hover-background: #952d2d;
--app-action-danger-hover-border: #952d2d;
--app-action-danger-focus-color: rgba(179, 54, 54, 0.5);
--app-action-danger-outline-background: rgba(179, 54, 54, 0.2);
feat(tenant): per-theme logos + file-upload + button UI polish Replace the single tenant avatar with a pair of theme-scoped brand logos. Render only the theme-matching <img> server-side and swap src on theme toggle via a JS hook — no reload, no double request, no CSS tricks. Tenant logos - TenantLogoService (ImageUploadTrait) with theme whitelist and per-theme storage storage/tenants/{uuid}/logo/{light|dark}/, SIZES 128/256/512 - Public serving endpoint auth/tenant-logo-file so login can show the logo pre-auth; matching authenticated admin preview endpoint - appTenantLogoUrl(?size, ?theme) with 4-step fallback cascade; PDF + mail always request 'light' - Admin tenant edit: avatar block replaced by "Tenant logos" details block inside the Master-data tab, two side-by-side slots via Pico .grid with the core app-file-upload partial - Policy rename ABILITY_ADMIN_TENANTS_AVATAR_VIEW -> LOGO_VIEW, action routes logo / logo-delete / logo-file with theme body/query param - API endpoint path kept (backward compat), internals on new service - CLI tenant:logo-migrate-avatars moves legacy avatar/ -> logo/light/ idempotently (--dry-run, --yes, --cleanup) - i18n "Tenant image" removed, 12 new keys synced across de/en File upload component - Full-width preview + filename/actions below (3D stack layout) - Fixed 16:9 aspect ratio with 1rem inner padding for consistent preview size across any logo aspect - Transparency checker pattern as background so black logos stay visible on dark mode and white logos on light mode - form="" + deleteFormId support so the partial works with barrier forms inside another form Buttons - width:100% dropped from button[type="submit"]; scoped back via .login-main for the auth-flow primary CTA - .outline base rule now tints background via color-mix of --app-color so secondary/primary/danger outlines all gain a subtle surface - .outline.secondary restyled Stripe-style in both themes: solid white chip with soft shadow in light, solid elevated dark chip with white text in dark; neutral border replaces role-colored border - .app-action-success/.app-action-danger outlines get color-mix bg + theme-aware outline-text tokens for stronger contrast - Filled .primary/.app-action-success/.app-action-danger get raised box-shadow (inset highlight + drop) — opt-in via class so chrome buttons stay flat - Dropped the legacy .secondary utility that was clobbering the custom-property cascade with a hardcoded muted color Theme swap - Logo img carries data-src-light + data-src-dark; theme-toggle JS swaps src when data-theme changes, keeping the topbar/login logo in sync without a page reload Quality gates: PHPUnit (2045), PHPStan L5, CS-Fixer, docs link/drift, codex skills sync — all green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 20:25:53 +02:00
--app-action-danger-outline-color: #e06666;
--app-switch-background-color: #3a3a38;
2026-02-04 23:31:53 +01:00
--app-switch-checked-background-color: var(--app-primary-background);
--app-switch-color: #fff;
--app-switch-thumb-box-shadow: 0 0 0 rgba(0, 0, 0, 0);
--app-range-border-color: #2f2f2d;
--app-range-active-border-color: #383835;
2026-02-04 23:31:53 +01:00
--app-range-thumb-border-color: var(--app-background-color);
--app-range-thumb-color: var(--app-secondary-background);
--app-range-thumb-active-color: var(--app-primary-background);
--app-accordion-border-color: var(--app-muted-border-color);
--app-accordion-active-summary-color: var(--app-primary-hover);
--app-accordion-close-summary-color: var(--app-color);
--app-accordion-open-summary-color: var(--app-muted-color);
--app-card-background-color: #242422;
2026-02-04 23:31:53 +01:00
--app-card-border-color: var(--app-card-background-color);
--app-card-box-shadow: var(--app-box-shadow);
--app-card-sectioning-background-color: #2a2a28;
2026-03-09 21:57:52 +01:00
--app-details-card-open-summary-bg: var(--app-card-sectioning-background-color);
--app-dropdown-background-color: #242422;
--app-dropdown-border-color: #2f2f2d;
2026-02-04 23:31:53 +01:00
--app-dropdown-box-shadow: var(--app-box-shadow);
--app-dropdown-color: var(--app-color);
--app-dropdown-hover-background-color: #2f2f2d;
2026-02-04 23:31:53 +01:00
--app-loading-spinner-opacity: 0.5;
--app-modal-overlay-background-color: rgba(15, 15, 14, 0.75);
--app-progress-background-color: #2f2f2d;
2026-02-04 23:31:53 +01:00
--app-progress-color: var(--app-primary-background);
--app-icon-valid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(42, 123, 111)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");
--app-icon-invalid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(149.5, 74, 80)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E");
}
[data-theme="dark"]
2026-02-04 23:31:53 +01:00
input:is(
[type="submit"],
[type="button"],
[type="reset"],
[type="checkbox"],
[type="radio"],
[type="file"]
) {
--app-form-element-focus-color: var(--app-primary-focus);
}
:host,
:root {
--app-scrollbar-width: 0px;
}
}