feat(login): update to nextjs 16, improve caching, change default colors (#11629)

# Which Problems Are Solved

Updated the project to use the latest versions of Next.js and React, and
implemented a SWR caching strategy for key settings API calls to improve
performance. Adjusted configuration to ensure compatibility with Next.js
16.

# How the Problems Are Solved

- Updated next to 16.1.6, react & react-dom to 19.2.4, and next-intl to
4.8.3.
- Implemented `API_CACHE_ENABLED` and `API_CACHE_CONFIG` (replacing
deprecated unstable_cacheLife) for:
  - getBrandingSettings
  - getSecuritySettings
  - getPasswordComplexitySettings
  - getLoginSettings ...
- Updated default theme colors from blue to a more neutral black and
white

# Additional changes

- Updated login docker image to node 24

---------

Co-authored-by: Ramon <[email protected]>
This commit is contained in:
Max Peintner
2026-03-17 16:01:50 +01:00
committed by GitHub
co-authored by Ramon
parent fca2589fc3
commit a864434b4b
17 changed files with 330 additions and 491 deletions
@@ -1,4 +1,9 @@
import { DynamicTheme } from "@/components/dynamic-theme";
import { IdpProcessHandler } from "@/components/idp-process-handler";
import { getServiceConfig } from "@/lib/service-url";
import { getBrandingSettings, getDefaultOrg } from "@/lib/zitadel";
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
import { headers } from "next/headers";
/**
* This page handles the initial IDP callback with the single-use token.
@@ -20,17 +25,32 @@ export default async function ProcessPage(props: {
throw new Error("Missing required IDP callback parameters");
}
const _headers = await headers();
const { serviceConfig } = getServiceConfig(_headers);
let defaultOrganization;
if (!organization) {
const org: Organization | null = await getDefaultOrg({ serviceConfig });
if (org) {
defaultOrganization = org.id;
}
}
const branding = await getBrandingSettings({ serviceConfig, organization: organization ?? defaultOrganization });
return (
<IdpProcessHandler
provider={provider}
id={id}
token={token}
requestId={requestId}
organization={organization}
link={link}
sessionId={linkToSessionId}
linkFingerprint={linkFingerprint}
postErrorRedirectUrl={postErrorRedirectUrl}
/>
<DynamicTheme branding={branding}>
<IdpProcessHandler
provider={provider}
id={id}
token={token}
requestId={requestId}
organization={organization}
link={link}
sessionId={linkToSessionId}
linkFingerprint={linkFingerprint}
postErrorRedirectUrl={postErrorRedirectUrl}
/>
</DynamicTheme>
);
}
-2
View File
@@ -10,8 +10,6 @@ import { NextRequest, NextResponse } from "next/server";
export const dynamic = "force-dynamic";
export const revalidate = false;
export const fetchCache = "default-no-store";
// Add this to prevent RSC requests
export const runtime = "nodejs";
async function loadSessions({ serviceConfig, ids }: { serviceConfig: ServiceConfig; ids: string[] }): Promise<Session[]> {
const response = await listSessions({ serviceConfig, ids: ids.filter((id: string | undefined) => !!id) });
-1
View File
@@ -10,7 +10,6 @@ import {
import { metrics, SpanStatusCode, trace } from "@opentelemetry/api";
import { NextResponse } from "next/server";
export const runtime = "nodejs";
const logger = createLogger("otel-test");
export async function GET() {
@@ -94,7 +94,7 @@ export function IdpProcessHandler({
}, [provider, id, token, requestId, organization, link, sessionId, linkFingerprint, postErrorRedirectUrl, router, t]);
return (
<div className="flex min-h-screen items-center justify-center">
<div className="flex items-center justify-center">
{samlData && <AutoSubmitForm url={samlData.url} fields={samlData.fields} />}
{loading && (
<div className="flex flex-col items-center space-y-4">
+2 -2
View File
@@ -30,13 +30,13 @@ export type ColorMap = {
[_key in MapName]: Color[];
};
export const DARK_PRIMARY = "#2073c4";
export const DARK_PRIMARY = "#eeeeee";
export const PRIMARY = "#5469d4";
export const DARK_WARN = "#ff3b5b";
export const WARN = "#cd3d56";
export const DARK_BACKGROUND = "#111827";
export const DARK_BACKGROUND = "#252526";
export const BACKGROUND = "#fafafa";
export const DARK_TEXT = "#ffffff";
+6
View File
@@ -78,6 +78,12 @@ describe("Theme Configuration", () => {
describe("getThemeConfig", () => {
it("should return default theme when no environment variables are set", () => {
delete process.env.NEXT_PUBLIC_THEME_ROUNDNESS;
delete process.env.NEXT_PUBLIC_THEME_LAYOUT;
delete process.env.NEXT_PUBLIC_THEME_APPEARANCE;
delete process.env.NEXT_PUBLIC_THEME_SPACING;
delete process.env.NEXT_PUBLIC_THEME_BACKGROUND_IMAGE;
const config = getThemeConfig();
expect(config.roundness).toBe(DEFAULT_THEME.roundness);
+177 -83
View File
@@ -30,7 +30,7 @@ import {
VerifyU2FRegistrationRequest,
} from "@zitadel/proto/zitadel/user/v2/user_service_pb";
import { getTranslations } from "next-intl/server";
import { unstable_cacheLife as cacheLife } from "next/cache";
import { getUserAgent } from "./fingerprint";
import { createLogger } from "./logger";
@@ -40,13 +40,55 @@ import { createServiceForHost } from "./service";
const logger = createLogger("zitadel");
const useCache = process.env.DEBUG !== "true";
const useCache = process.env.API_CACHE_ENABLED !== "false";
async function cacheWrapper<T>(callback: Promise<T>) {
"use cache";
cacheLife("hours");
let cacheConfig: Record<string, number> = {};
try {
if (process.env.API_CACHE_CONFIG) {
cacheConfig = JSON.parse(process.env.API_CACHE_CONFIG);
}
} catch (e) {
console.error("Failed to parse API_CACHE_CONFIG", e);
}
return callback;
const defaultCacheTTL = (cacheConfig.defaultMinutes ?? 15) * 60 * 1000; // 15 mins default
const longCacheTTL = (cacheConfig.longMinutes ?? 60) * 60 * 1000; // 1 hour default
/**
* Helper to determine the TTL for a specific API method.
* Allows overriding per-resource via the API_CACHE_CONFIG environment variable.
*/
function getTTLForKey(keyPrefix: string, fallbackTtl: number) {
const configTTL = cacheConfig[keyPrefix];
if (typeof configTTL === "number" && !isNaN(configTTL)) {
return configTTL * 60 * 1000; // Config expects minutes
}
return fallbackTtl;
}
const promiseCache = new Map<string, { promise: Promise<any>; expiresAt: number }>();
/**
* A stale-while-revalidate in-memory cache to keep data fresh and deduplicate concurrent requests.
* We cache the Promise, so concurrent requests share the exact same execution.
*/
function freshCache<T>(key: string, fetcher: () => Promise<T>, ttlMs: number): Promise<T> {
if (!useCache) {
return fetcher();
}
const now = Date.now();
const cached = promiseCache.get(key);
if (cached && now < cached.expiresAt) {
return cached.promise;
}
const promise = fetcher();
promiseCache.set(key, { promise, expiresAt: now + ttlMs });
promise.catch(() => promiseCache.delete(key));
return promise;
}
export async function getHostedLoginTranslation({
@@ -57,29 +99,35 @@ export async function getHostedLoginTranslation({
organization?: string;
locale?: string;
}>) {
const settingsService: Client<typeof SettingsService> = await createServiceForHost(SettingsService, serviceConfig);
const fetcher = async () => {
const settingsService: Client<typeof SettingsService> = await createServiceForHost(SettingsService, serviceConfig);
const callback = settingsService
.getHostedLoginTranslation(
{
level: organization
? {
case: "organizationId",
value: organization,
}
: {
case: "instance",
value: true,
},
locale: locale,
},
{},
)
.then((resp) => {
return resp.translations ? resp.translations : undefined;
});
return settingsService
.getHostedLoginTranslation(
{
level: organization
? {
case: "organizationId",
value: organization,
}
: {
case: "instance",
value: true,
},
locale: locale,
},
{},
)
.then((resp) => {
return resp.translations ? resp.translations : undefined;
});
};
return useCache ? cacheWrapper(callback) : callback;
return freshCache(
`getHostedLoginTranslation-${organization || "instance"}-${locale || "default"}`,
fetcher,
getTTLForKey("getHostedLoginTranslation", longCacheTTL),
);
}
export async function getBrandingSettings({
@@ -88,13 +136,19 @@ export async function getBrandingSettings({
}: WithServiceConfig<{
organization?: string;
}>) {
const settingsService: Client<typeof SettingsService> = await createServiceForHost(SettingsService, serviceConfig);
const fetcher = async () => {
const settingsService: Client<typeof SettingsService> = await createServiceForHost(SettingsService, serviceConfig);
const callback = settingsService
.getBrandingSettings({ ctx: makeReqCtx(organization) }, {})
.then((resp) => (resp.settings ? resp.settings : undefined));
return settingsService
.getBrandingSettings({ ctx: makeReqCtx(organization) }, {})
.then((resp) => (resp.settings ? resp.settings : undefined));
};
return useCache ? cacheWrapper(callback) : callback;
return freshCache(
`getBrandingSettings-${organization || "instance"}`,
fetcher,
getTTLForKey("getBrandingSettings", longCacheTTL),
);
}
export async function getLoginSettings({
@@ -103,41 +157,61 @@ export async function getLoginSettings({
}: WithServiceConfig<{
organization?: string;
}>) {
const settingsService: Client<typeof SettingsService> = await createServiceForHost(SettingsService, serviceConfig);
const fetcher = async () => {
const settingsService: Client<typeof SettingsService> = await createServiceForHost(SettingsService, serviceConfig);
const callback = settingsService
.getLoginSettings({ ctx: makeReqCtx(organization) }, {})
.then((resp) => (resp.settings ? resp.settings : undefined));
return settingsService
.getLoginSettings({ ctx: makeReqCtx(organization) }, {})
.then((resp) => (resp.settings ? resp.settings : undefined));
};
return useCache ? cacheWrapper(callback) : callback;
return freshCache(
`getLoginSettings-${organization || "instance"}`,
fetcher,
getTTLForKey("getLoginSettings", defaultCacheTTL),
);
}
export async function getSecuritySettings({ serviceConfig }: WithServiceConfig) {
const settingsService: Client<typeof SettingsService> = await createServiceForHost(SettingsService, serviceConfig);
const fetcher = async () => {
const settingsService: Client<typeof SettingsService> = await createServiceForHost(SettingsService, serviceConfig);
const callback = settingsService.getSecuritySettings({}).then((resp) => (resp.settings ? resp.settings : undefined));
return settingsService.getSecuritySettings({}).then((resp) => (resp.settings ? resp.settings : undefined));
};
return useCache ? cacheWrapper(callback) : callback;
return freshCache(`getSecuritySettings-instance`, fetcher, getTTLForKey("getSecuritySettings", defaultCacheTTL));
}
export async function getLockoutSettings({ serviceConfig, orgId }: WithServiceConfig<{ orgId?: string }>) {
const settingsService: Client<typeof SettingsService> = await createServiceForHost(SettingsService, serviceConfig);
const fetcher = async () => {
const settingsService: Client<typeof SettingsService> = await createServiceForHost(SettingsService, serviceConfig);
const callback = settingsService
.getLockoutSettings({ ctx: makeReqCtx(orgId) }, {})
.then((resp) => (resp.settings ? resp.settings : undefined));
return settingsService
.getLockoutSettings({ ctx: makeReqCtx(orgId) }, {})
.then((resp) => (resp.settings ? resp.settings : undefined));
};
return useCache ? cacheWrapper(callback) : callback;
return freshCache(
`getLockoutSettings-${orgId || "instance"}`,
fetcher,
getTTLForKey("getLockoutSettings", defaultCacheTTL),
);
}
export async function getPasswordExpirySettings({ serviceConfig, orgId }: WithServiceConfig<{ orgId?: string }>) {
const settingsService: Client<typeof SettingsService> = await createServiceForHost(SettingsService, serviceConfig);
const fetcher = async () => {
const settingsService: Client<typeof SettingsService> = await createServiceForHost(SettingsService, serviceConfig);
const callback = settingsService
.getPasswordExpirySettings({ ctx: makeReqCtx(orgId) }, {})
.then((resp) => (resp.settings ? resp.settings : undefined));
return settingsService
.getPasswordExpirySettings({ ctx: makeReqCtx(orgId) }, {})
.then((resp) => (resp.settings ? resp.settings : undefined));
};
return useCache ? cacheWrapper(callback) : callback;
return freshCache(
`getPasswordExpirySettings-${orgId || "instance"}`,
fetcher,
getTTLForKey("getPasswordExpirySettings", defaultCacheTTL),
);
}
export async function listIDPLinks({ serviceConfig, userId }: WithServiceConfig<{ userId: string }>) {
@@ -165,16 +239,18 @@ export async function registerTOTP({ serviceConfig, userId }: WithServiceConfig<
}
export async function getAllowedLanguages({ serviceConfig }: WithServiceConfig) {
const settingsService: Client<typeof SettingsService> = await createServiceForHost(SettingsService, serviceConfig);
const fetcher = async () => {
const settingsService: Client<typeof SettingsService> = await createServiceForHost(SettingsService, serviceConfig);
const callback = settingsService.getGeneralSettings({}, {}).then((resp) => {
return {
allowedLanguages: resp.allowedLanguages,
defaultLanguage: resp.defaultLanguage,
};
});
return settingsService.getGeneralSettings({}, {}).then((resp) => {
return {
allowedLanguages: resp.allowedLanguages,
defaultLanguage: resp.defaultLanguage,
};
});
};
return useCache ? cacheWrapper(callback) : callback;
return freshCache(`getGeneralSettings-instance`, fetcher, getTTLForKey("getGeneralSettings", longCacheTTL));
}
export async function getLegalAndSupportSettings({
@@ -183,13 +259,19 @@ export async function getLegalAndSupportSettings({
}: WithServiceConfig<{
organization?: string;
}>) {
const settingsService: Client<typeof SettingsService> = await createServiceForHost(SettingsService, serviceConfig);
const fetcher = async () => {
const settingsService: Client<typeof SettingsService> = await createServiceForHost(SettingsService, serviceConfig);
const callback = settingsService
.getLegalAndSupportSettings({ ctx: makeReqCtx(organization) }, {})
.then((resp) => (resp.settings ? resp.settings : undefined));
return settingsService
.getLegalAndSupportSettings({ ctx: makeReqCtx(organization) }, {})
.then((resp) => (resp.settings ? resp.settings : undefined));
};
return useCache ? cacheWrapper(callback) : callback;
return freshCache(
`getLegalAndSupportSettings-${organization || "instance"}`,
fetcher,
getTTLForKey("getLegalAndSupportSettings", longCacheTTL),
);
}
export async function getPasswordComplexitySettings({
@@ -198,13 +280,19 @@ export async function getPasswordComplexitySettings({
}: WithServiceConfig<{
organization?: string;
}>) {
const settingsService: Client<typeof SettingsService> = await createServiceForHost(SettingsService, serviceConfig);
const fetcher = async () => {
const settingsService: Client<typeof SettingsService> = await createServiceForHost(SettingsService, serviceConfig);
const callback = settingsService
.getPasswordComplexitySettings({ ctx: makeReqCtx(organization) })
.then((resp) => (resp.settings ? resp.settings : undefined));
return settingsService
.getPasswordComplexitySettings({ ctx: makeReqCtx(organization) })
.then((resp) => (resp.settings ? resp.settings : undefined));
};
return useCache ? cacheWrapper(callback) : callback;
return freshCache(
`getPasswordComplexitySettings-${organization || "instance"}`,
fetcher,
getTTLForKey("getPasswordComplexitySettings", defaultCacheTTL),
);
}
export async function createSessionFromChecksAndChallenges({
@@ -740,23 +828,29 @@ export async function searchUsers({
}
export async function getDefaultOrg({ serviceConfig }: WithServiceConfig): Promise<Organization | null> {
const orgService: Client<typeof OrganizationService> = await createServiceForHost(OrganizationService, serviceConfig);
const fetcher = async () => {
const orgService: Client<typeof OrganizationService> = await createServiceForHost(OrganizationService, serviceConfig);
return orgService
.listOrganizations(
{
queries: [
{
query: {
case: "defaultQuery",
value: {},
return orgService
.listOrganizations(
{
queries: [
{
query: {
case: "defaultQuery",
value: {},
},
},
},
],
},
{},
)
.then((resp) => (resp?.result && resp.result[0] ? resp.result[0] : null));
],
},
{},
)
.then((resp) => (resp?.result && resp.result[0] ? resp.result[0] : null));
};
return useCache
? freshCache(`getDefaultOrg-${"instance"}`, fetcher, getTTLForKey("getDefaultOrg", defaultCacheTTL))
: fetcher();
}
export async function getOrgsByDomain({ serviceConfig, domain }: WithServiceConfig<{ domain: string }>) {
@@ -6,8 +6,6 @@ import { buildCSP } from "./lib/csp";
import { createLogger } from "./lib/logger";
import { getServiceConfig } from "./lib/service-url";
export const runtime = "nodejs";
const logger = createLogger("middleware");
export const config = {
matcher: ["/.well-known/:path*", "/oauth/:path*", "/oidc/:path*", "/idps/callback/:path*", "/saml/:path*", "/:path*"],
@@ -31,7 +29,7 @@ async function loadSecuritySettings(request: NextRequest): Promise<SecuritySetti
return response.settings;
}
export async function middleware(request: NextRequest) {
export async function proxy(request: NextRequest) {
// Add the original URL as a header to all requests
const requestHeaders = new Headers(request.headers);
-3
View File
@@ -1,6 +1,3 @@
// include styles from the ui package
@use "./vars.scss";
@tailwind base;
@tailwind components;
@tailwind utilities;
-174
View File
@@ -1,174 +0,0 @@
:root {
--theme-dark-primary-50: #f1f7fd;
--theme-dark-primary-contrast-50: hsla(0, 0%, 0%, 0.87);
--theme-dark-primary-100: #afd1f2;
--theme-dark-primary-contrast-100: hsla(0, 0%, 0%, 0.87);
--theme-dark-primary-200: #7fb5ea;
--theme-dark-primary-contrast-200: hsla(0, 0%, 0%, 0.87);
--theme-dark-primary-300: #4192e0;
--theme-dark-primary-contrast-300: hsla(0, 0%, 0%, 0.87);
--theme-dark-primary-400: #2782dc;
--theme-dark-primary-contrast-400: hsla(0, 0%, 0%, 0.87);
--theme-dark-primary-500: #2073c4;
--theme-dark-primary-contrast-500: #ffffff;
--theme-dark-primary-600: #1c64aa;
--theme-dark-primary-contrast-600: #ffffff;
--theme-dark-primary-700: #17548f;
--theme-dark-primary-contrast-700: #ffffff;
--theme-dark-primary-800: #134575;
--theme-dark-primary-contrast-800: #ffffff;
--theme-dark-primary-900: #0f355b;
--theme-dark-primary-contrast-900: #ffffff;
--theme-dark-primary-A100: #e4f2ff;
--theme-dark-primary-contrast-A100: hsla(0, 0%, 0%, 0.87);
--theme-dark-primary-A200: #7ebfff;
--theme-dark-primary-contrast-A200: hsla(0, 0%, 0%, 0.87);
--theme-dark-primary-A400: #278df0;
--theme-dark-primary-contrast-A400: hsla(0, 0%, 0%, 0.87);
--theme-dark-primary-A700: #1d80e0;
--theme-dark-primary-contrast-A700: hsla(0, 0%, 0%, 0.87);
--theme-light-primary-50: #ffffff;
--theme-light-primary-contrast-50: hsla(0, 0%, 0%, 0.87);
--theme-light-primary-100: #ebedfa;
--theme-light-primary-contrast-100: hsla(0, 0%, 0%, 0.87);
--theme-light-primary-200: #bec6ef;
--theme-light-primary-contrast-200: hsla(0, 0%, 0%, 0.87);
--theme-light-primary-300: #8594e0;
--theme-light-primary-contrast-300: hsla(0, 0%, 0%, 0.87);
--theme-light-primary-400: #6c7eda;
--theme-light-primary-contrast-400: hsla(0, 0%, 0%, 0.87);
--theme-light-primary-500: #5469d4;
--theme-light-primary-contrast-500: #ffffff;
--theme-light-primary-600: #3c54ce;
--theme-light-primary-contrast-600: #ffffff;
--theme-light-primary-700: #2f46bc;
--theme-light-primary-contrast-700: #ffffff;
--theme-light-primary-800: #293da3;
--theme-light-primary-contrast-800: #ffffff;
--theme-light-primary-900: #23348b;
--theme-light-primary-contrast-900: #ffffff;
--theme-light-primary-A100: #ffffff;
--theme-light-primary-contrast-A100: hsla(0, 0%, 0%, 0.87);
--theme-light-primary-A200: #c5cefc;
--theme-light-primary-contrast-A200: hsla(0, 0%, 0%, 0.87);
--theme-light-primary-A400: #7085ea;
--theme-light-primary-contrast-A400: hsla(0, 0%, 0%, 0.87);
--theme-light-primary-A700: #6478de;
--theme-light-primary-contrast-A700: hsla(0, 0%, 0%, 0.87);
--theme-dark-warn-50: #ffffff;
--theme-dark-warn-contrast-50: hsla(0, 0%, 0%, 0.87);
--theme-dark-warn-100: #fff8f9;
--theme-dark-warn-contrast-100: hsla(0, 0%, 0%, 0.87);
--theme-dark-warn-200: #ffc0ca;
--theme-dark-warn-contrast-200: hsla(0, 0%, 0%, 0.87);
--theme-dark-warn-300: #ff788e;
--theme-dark-warn-contrast-300: hsla(0, 0%, 0%, 0.87);
--theme-dark-warn-400: #ff5a75;
--theme-dark-warn-contrast-400: hsla(0, 0%, 0%, 0.87);
--theme-dark-warn-500: #ff3b5b;
--theme-dark-warn-contrast-500: hsla(0, 0%, 0%, 0.87);
--theme-dark-warn-600: #ff1c41;
--theme-dark-warn-contrast-600: hsla(0, 0%, 0%, 0.87);
--theme-dark-warn-700: #fd0029;
--theme-dark-warn-contrast-700: hsla(0, 0%, 0%, 0.87);
--theme-dark-warn-800: #de0024;
--theme-dark-warn-contrast-800: #ffffff;
--theme-dark-warn-900: #c0001f;
--theme-dark-warn-contrast-900: #ffffff;
--theme-dark-warn-A100: #ffffff;
--theme-dark-warn-contrast-A100: hsla(0, 0%, 0%, 0.87);
--theme-dark-warn-A200: #ffd4db;
--theme-dark-warn-contrast-A200: hsla(0, 0%, 0%, 0.87);
--theme-dark-warn-A400: #ff6e86;
--theme-dark-warn-contrast-A400: hsla(0, 0%, 0%, 0.87);
--theme-dark-warn-A700: #ff5470;
--theme-dark-warn-contrast-A700: hsla(0, 0%, 0%, 0.87);
--theme-light-warn-50: #ffffff;
--theme-light-warn-contrast-50: hsla(0, 0%, 0%, 0.87);
--theme-light-warn-100: #f4d3d9;
--theme-light-warn-contrast-100: hsla(0, 0%, 0%, 0.87);
--theme-light-warn-200: #e8a6b2;
--theme-light-warn-contrast-200: hsla(0, 0%, 0%, 0.87);
--theme-light-warn-300: #da6e80;
--theme-light-warn-contrast-300: hsla(0, 0%, 0%, 0.87);
--theme-light-warn-400: #d3556b;
--theme-light-warn-contrast-400: hsla(0, 0%, 0%, 0.87);
--theme-light-warn-500: #cd3d56;
--theme-light-warn-contrast-500: #ffffff;
--theme-light-warn-600: #bb3048;
--theme-light-warn-contrast-600: #ffffff;
--theme-light-warn-700: #a32a3f;
--theme-light-warn-contrast-700: #ffffff;
--theme-light-warn-800: #8a2436;
--theme-light-warn-contrast-800: #ffffff;
--theme-light-warn-900: #721d2c;
--theme-light-warn-contrast-900: #ffffff;
--theme-light-warn-A100: #ffffff;
--theme-light-warn-contrast-A100: hsla(0, 0%, 0%, 0.87);
--theme-light-warn-A200: #faa9b7;
--theme-light-warn-contrast-A200: hsla(0, 0%, 0%, 0.87);
--theme-light-warn-A400: #e65770;
--theme-light-warn-contrast-A400: hsla(0, 0%, 0%, 0.87);
--theme-light-warn-A700: #d84c64;
--theme-light-warn-contrast-A700: hsla(0, 0%, 0%, 0.87);
--theme-dark-background-50: #7c93c6;
--theme-dark-background-contrast-50: hsla(0, 0%, 0%, 0.87);
--theme-dark-background-100: #4a69aa;
--theme-dark-background-contrast-100: #ffffff;
--theme-dark-background-200: #395183;
--theme-dark-background-contrast-200: #ffffff;
--theme-dark-background-300: #243252;
--theme-dark-background-contrast-300: #ffffff;
--theme-dark-background-400: #1a253c;
--theme-dark-background-contrast-400: #ffffff;
--theme-dark-background-500: #111827;
--theme-dark-background-contrast-500: #ffffff;
--theme-dark-background-600: #080b12;
--theme-dark-background-contrast-600: #ffffff;
--theme-dark-background-700: #000000;
--theme-dark-background-contrast-700: #ffffff;
--theme-dark-background-800: #000000;
--theme-dark-background-contrast-800: #ffffff;
--theme-dark-background-900: #000000;
--theme-dark-background-contrast-900: #ffffff;
--theme-dark-background-A100: #5782e0;
--theme-dark-background-contrast-A100: hsla(0, 0%, 0%, 0.87);
--theme-dark-background-A200: #204eb1;
--theme-dark-background-contrast-A200: #ffffff;
--theme-dark-background-A400: #182b53;
--theme-dark-background-contrast-A400: #ffffff;
--theme-dark-background-A700: #17223b;
--theme-dark-background-contrast-A700: #ffffff;
--theme-light-background-50: #ffffff;
--theme-light-background-contrast-50: hsla(0, 0%, 0%, 0.87);
--theme-light-background-100: #ffffff;
--theme-light-background-contrast-100: hsla(0, 0%, 0%, 0.87);
--theme-light-background-200: #ffffff;
--theme-light-background-contrast-200: hsla(0, 0%, 0%, 0.87);
--theme-light-background-300: #ffffff;
--theme-light-background-contrast-300: hsla(0, 0%, 0%, 0.87);
--theme-light-background-400: #ffffff;
--theme-light-background-contrast-400: hsla(0, 0%, 0%, 0.87);
--theme-light-background-500: #fafafa;
--theme-light-background-contrast-500: hsla(0, 0%, 0%, 0.87);
--theme-light-background-600: #ebebeb;
--theme-light-background-contrast-600: hsla(0, 0%, 0%, 0.87);
--theme-light-background-700: #dbdbdb;
--theme-light-background-contrast-700: hsla(0, 0%, 0%, 0.87);
--theme-light-background-800: #cccccc;
--theme-light-background-contrast-800: hsla(0, 0%, 0%, 0.87);
--theme-light-background-900: #bdbdbd;
--theme-light-background-contrast-900: hsla(0, 0%, 0%, 0.87);
--theme-light-background-A100: #ffffff;
--theme-light-background-contrast-A100: hsla(0, 0%, 0%, 0.87);
--theme-light-background-A200: #ffffff;
--theme-light-background-contrast-A200: hsla(0, 0%, 0%, 0.87);
--theme-light-background-A400: #ffffff;
--theme-light-background-contrast-A400: hsla(0, 0%, 0%, 0.87);
--theme-light-background-A700: #ffffff;
--theme-light-background-contrast-A700: hsla(0, 0%, 0%, 0.87);
--theme-dark-text: #ffffff;
--theme-dark-secondary-text: #ffffffc7;
--theme-light-text: #000000;
--theme-light-secondary-text: #000000c7;
}