@zitadel/components 1.0.0-alpha.22 → 1.0.0-alpha.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2638,6 +2638,7 @@ let ZlField = class ZlField extends i$2 {
2638
2638
  class="zr-field__trailing-action"
2639
2639
  part="trailing-action"
2640
2640
  aria-label="Clear"
2641
+ tabindex="-1"
2641
2642
  ?disabled=${this.disabled}
2642
2643
  @click=${this.handleClear}
2643
2644
  >
@@ -4023,18 +4024,28 @@ function validateBranding(input, context = {}) {
4023
4024
  out.layout = CreateFlow201BrandingLayout.centered;
4024
4025
  }
4025
4026
  out.logo_url = sanitiseUrl(out.logo_url, "logo_url", issues, { allowed: allowLoopbackHttp });
4026
- out.font_url = sanitiseUrl(out.font_url, "font_url", issues);
4027
+ if (out.typography?.font_url) out.typography = {
4028
+ ...out.typography,
4029
+ font_url: sanitiseUrl(out.typography.font_url, "typography.font_url", issues)
4030
+ };
4027
4031
  out.hero_url = sanitiseUrl(out.hero_url, "hero_url", issues, { allowed: allowLoopbackHttp });
4028
- if (out.assets) out.assets = {
4029
- logo_dark: sanitiseUrl(out.assets.logo_dark, "assets.logo_dark", issues),
4030
- favicon: sanitiseUrl(out.assets.favicon, "assets.favicon", issues),
4031
- background_image: sanitiseUrl(out.assets.background_image, "assets.background_image", issues)
4032
+ if (out.theme?.light || out.theme?.dark) out.theme = {
4033
+ ...out.theme,
4034
+ ...sanitiseSideLogo(out.theme.light, "light", issues, allowLoopbackHttp),
4035
+ ...sanitiseSideLogo(out.theme.dark, "dark", issues, allowLoopbackHttp)
4032
4036
  };
4033
4037
  return {
4034
4038
  branding: out,
4035
4039
  issues
4036
4040
  };
4037
4041
  }
4042
+ function sanitiseSideLogo(side, name, issues, allowLoopbackHttp) {
4043
+ if (!side) return {};
4044
+ return { [name]: {
4045
+ ...side,
4046
+ logo_url: sanitiseUrl(side.logo_url, `theme.${name}.logo_url`, issues, { allowed: allowLoopbackHttp })
4047
+ } };
4048
+ }
4038
4049
  function sanitiseUrl(value, field, issues, loopbackHttp) {
4039
4050
  if (value == null || value === "") return;
4040
4051
  try {
@@ -4052,6 +4063,34 @@ function sanitiseUrl(value, field, issues, loopbackHttp) {
4052
4063
  }
4053
4064
  }
4054
4065
  //#endregion
4066
+ //#region src/orchestrator/branding.ts
4067
+ /**
4068
+ * Which sides the revision actually publishes. A side is published when the
4069
+ * revision names it at all — an empty side object still says "this surface is
4070
+ * mine", it just takes the maintained defaults for every key.
4071
+ */
4072
+ function publishedSides(branding) {
4073
+ const theme = branding?.theme;
4074
+ const sides = [];
4075
+ if (theme?.light) sides.push("light");
4076
+ if (theme?.dark) sides.push("dark");
4077
+ return sides;
4078
+ }
4079
+ /**
4080
+ * The mark for the resolved surface. A logo is pixels and is never recoloured,
4081
+ * so a side without its own file shows no mark rather than borrowing the other
4082
+ * side's. The legacy top-level `logo_url` is a single-mark fallback and applies
4083
+ * only when neither side names one.
4084
+ */
4085
+ function resolveLogoUrl(branding, theme) {
4086
+ if (!branding) return void 0;
4087
+ const sides = branding.theme;
4088
+ const sideLogo = theme === "light" ? sides?.light?.logo_url : sides?.dark?.logo_url;
4089
+ if (sideLogo) return sideLogo;
4090
+ if (sides?.light?.logo_url || sides?.dark?.logo_url) return void 0;
4091
+ return branding.logo_url;
4092
+ }
4093
+ //#endregion
4055
4094
  //#region src/manifests.ts
4056
4095
  /**
4057
4096
  * Aggregate registry of every shipped `<zl-*>` atom's manifest.
@@ -6982,7 +7021,7 @@ const minus = argumentsToNumber((v, arg) => v - arg);
6982
7021
  const plus = argumentsToNumber((lhs, rhs) => lhs + rhs);
6983
7022
  const modulo = argumentsToNumber((v, arg) => (v % arg + arg) % arg);
6984
7023
  const times = argumentsToNumber((v, arg) => v * arg);
6985
- function round(v, arg = 0) {
7024
+ function round$1(v, arg = 0) {
6986
7025
  v = toNumber(v);
6987
7026
  arg = toNumber(arg);
6988
7027
  const amp = Math.pow(10, arg);
@@ -7001,7 +7040,7 @@ var mathFilters = /* @__PURE__ */ Object.freeze({
7001
7040
  plus,
7002
7041
  modulo,
7003
7042
  times,
7004
- round
7043
+ round: round$1
7005
7044
  });
7006
7045
  const url_decode = (x) => decodeURIComponent(stringify$1(x)).replace(/\+/g, " ");
7007
7046
  const url_encode = (x) => encodeURIComponent(stringify$1(x)).replace(/%20/g, "+");
@@ -9087,7 +9126,7 @@ const en = {
9087
9126
  "error.password_required": "Please enter a password",
9088
9127
  "error.password_incorrect": "Wrong email or password.",
9089
9128
  "error.email_exists": "An account with this email already exists.",
9090
- /** Figma sign-in error `6602:180268` — inline on password field. */
9129
+ /** Sign-in error, inline on the password field. */
9091
9130
  "error.invalid_credentials": "Wrong email or password.",
9092
9131
  "error.required": "This field is required.",
9093
9132
  "error.field_required": "{0} is required.",
@@ -9410,16 +9449,17 @@ var zitadelNextGen_exports = /* @__PURE__ */ __exportAll({
9410
9449
  createFlowDefinition: () => createFlowDefinition,
9411
9450
  createGrant: () => createGrant,
9412
9451
  createHandoff: () => createHandoff,
9452
+ createIdp: () => createIdp,
9413
9453
  createProject: () => createProject,
9414
9454
  createRelease: () => createRelease,
9415
9455
  createSchema: () => createSchema,
9416
9456
  createSession: () => createSession,
9417
9457
  createTeam: () => createTeam,
9418
9458
  createUser: () => createUser,
9419
- deleteFlowDefinition: () => deleteFlowDefinition,
9420
9459
  deleteGrant: () => deleteGrant,
9421
9460
  deleteTeam: () => deleteTeam,
9422
9461
  deleteUserByID: () => deleteUserByID,
9462
+ deleteVariable: () => deleteVariable,
9423
9463
  exchangeHandoff: () => exchangeHandoff,
9424
9464
  finishUserPasskeyRegistration: () => finishUserPasskeyRegistration,
9425
9465
  getAuthAttempt: () => getAuthAttempt,
@@ -9434,16 +9474,17 @@ var zitadelNextGen_exports = /* @__PURE__ */ __exportAll({
9434
9474
  getCreateFlowUrl: () => getCreateFlowUrl,
9435
9475
  getCreateGrantUrl: () => getCreateGrantUrl,
9436
9476
  getCreateHandoffUrl: () => getCreateHandoffUrl,
9477
+ getCreateIdpUrl: () => getCreateIdpUrl,
9437
9478
  getCreateProjectUrl: () => getCreateProjectUrl,
9438
9479
  getCreateReleaseUrl: () => getCreateReleaseUrl,
9439
9480
  getCreateSchemaUrl: () => getCreateSchemaUrl,
9440
9481
  getCreateSessionUrl: () => getCreateSessionUrl,
9441
9482
  getCreateTeamUrl: () => getCreateTeamUrl,
9442
9483
  getCreateUserUrl: () => getCreateUserUrl,
9443
- getDeleteFlowDefinitionUrl: () => getDeleteFlowDefinitionUrl,
9444
9484
  getDeleteGrantUrl: () => getDeleteGrantUrl,
9445
9485
  getDeleteTeamUrl: () => getDeleteTeamUrl,
9446
9486
  getDeleteUserByIDUrl: () => getDeleteUserByIDUrl,
9487
+ getDeleteVariableUrl: () => getDeleteVariableUrl,
9447
9488
  getEnvironmentByName: () => getEnvironmentByName,
9448
9489
  getEvent: () => getEvent,
9449
9490
  getExchangeHandoffUrl: () => getExchangeHandoffUrl,
@@ -9460,6 +9501,8 @@ var zitadelNextGen_exports = /* @__PURE__ */ __exportAll({
9460
9501
  getGetFlowStepUrl: () => getGetFlowStepUrl,
9461
9502
  getGetGrantUrl: () => getGetGrantUrl,
9462
9503
  getGetHealthUrl: () => getGetHealthUrl,
9504
+ getGetIdpByIdUrl: () => getGetIdpByIdUrl,
9505
+ getGetIdpRevisionByIdUrl: () => getGetIdpRevisionByIdUrl,
9463
9506
  getGetLiveUrl: () => getGetLiveUrl,
9464
9507
  getGetMySessionUrl: () => getGetMySessionUrl,
9465
9508
  getGetMyUserUrl: () => getGetMyUserUrl,
@@ -9470,14 +9513,20 @@ var zitadelNextGen_exports = /* @__PURE__ */ __exportAll({
9470
9513
  getGetSessionUrl: () => getGetSessionUrl,
9471
9514
  getGetTeamUrl: () => getGetTeamUrl,
9472
9515
  getGetUserByIDUrl: () => getGetUserByIDUrl,
9516
+ getGetVariableUrl: () => getGetVariableUrl,
9517
+ getGetVariablesUrl: () => getGetVariablesUrl,
9473
9518
  getGrant: () => getGrant,
9474
9519
  getHealth: () => getHealth,
9520
+ getIdpById: () => getIdpById,
9521
+ getIdpRevisionById: () => getIdpRevisionById,
9475
9522
  getInitClaimUrl: () => getInitClaimUrl,
9476
9523
  getIssueChallengeUrl: () => getIssueChallengeUrl,
9477
9524
  getListBrandingUrl: () => getListBrandingUrl,
9478
9525
  getListEnvironmentsUrl: () => getListEnvironmentsUrl,
9479
9526
  getListEventsUrl: () => getListEventsUrl,
9480
9527
  getListFlowDefinitionsUrl: () => getListFlowDefinitionsUrl,
9528
+ getListIdpRevisionsUrl: () => getListIdpRevisionsUrl,
9529
+ getListMyProjectsUrl: () => getListMyProjectsUrl,
9481
9530
  getListReleasesUrl: () => getListReleasesUrl,
9482
9531
  getListSchemasUrl: () => getListSchemasUrl,
9483
9532
  getListUserPasskeysUrl: () => getListUserPasskeysUrl,
@@ -9485,9 +9534,12 @@ var zitadelNextGen_exports = /* @__PURE__ */ __exportAll({
9485
9534
  getLive: () => getLive,
9486
9535
  getMySession: () => getMySession,
9487
9536
  getMyUser: () => getMyUser,
9537
+ getPatchMyUserUrl: () => getPatchMyUserUrl,
9488
9538
  getPatchProjectUrl: () => getPatchProjectUrl,
9539
+ getPatchUserByIDUrl: () => getPatchUserByIDUrl,
9489
9540
  getProject: () => getProject,
9490
9541
  getQueryGrantsUrl: () => getQueryGrantsUrl,
9542
+ getQueryIdpsUrl: () => getQueryIdpsUrl,
9491
9543
  getQueryProjectsUrl: () => getQueryProjectsUrl,
9492
9544
  getQuerySessionsUrl: () => getQuerySessionsUrl,
9493
9545
  getQueryTeamsUrl: () => getQueryTeamsUrl,
@@ -9501,9 +9553,11 @@ var zitadelNextGen_exports = /* @__PURE__ */ __exportAll({
9501
9553
  getSetUserPasswordUrl: () => getSetUserPasswordUrl,
9502
9554
  getSubmitFlowStepUrl: () => getSubmitFlowStepUrl,
9503
9555
  getTeam: () => getTeam,
9504
- getUpdateFlowDefinitionUrl: () => getUpdateFlowDefinitionUrl,
9505
9556
  getUpdateTeamUrl: () => getUpdateTeamUrl,
9557
+ getUpdateVariablesUrl: () => getUpdateVariablesUrl,
9506
9558
  getUserByID: () => getUserByID,
9559
+ getVariable: () => getVariable,
9560
+ getVariables: () => getVariables,
9507
9561
  getVerifyChallengeProofUrl: () => getVerifyChallengeProofUrl,
9508
9562
  initClaim: () => initClaim,
9509
9563
  issueChallenge: () => issueChallenge,
@@ -9511,12 +9565,17 @@ var zitadelNextGen_exports = /* @__PURE__ */ __exportAll({
9511
9565
  listEnvironments: () => listEnvironments,
9512
9566
  listEvents: () => listEvents,
9513
9567
  listFlowDefinitions: () => listFlowDefinitions,
9568
+ listIdpRevisions: () => listIdpRevisions,
9569
+ listMyProjects: () => listMyProjects,
9514
9570
  listReleases: () => listReleases,
9515
9571
  listSchemas: () => listSchemas,
9516
9572
  listUserPasskeys: () => listUserPasskeys,
9517
9573
  listUserTeams: () => listUserTeams,
9574
+ patchMyUser: () => patchMyUser,
9518
9575
  patchProject: () => patchProject,
9576
+ patchUserByID: () => patchUserByID,
9519
9577
  queryGrants: () => queryGrants,
9578
+ queryIdps: () => queryIdps,
9520
9579
  queryProjects: () => queryProjects,
9521
9580
  querySessions: () => querySessions,
9522
9581
  queryTeams: () => queryTeams,
@@ -9525,8 +9584,8 @@ var zitadelNextGen_exports = /* @__PURE__ */ __exportAll({
9525
9584
  revokeSession: () => revokeSession,
9526
9585
  setUserPassword: () => setUserPassword,
9527
9586
  submitFlowStep: () => submitFlowStep,
9528
- updateFlowDefinition: () => updateFlowDefinition,
9529
9587
  updateTeam: () => updateTeam,
9588
+ updateVariables: () => updateVariables,
9530
9589
  verifyChallengeProof: () => verifyChallengeProof
9531
9590
  });
9532
9591
  const getGetHealthUrl = () => {
@@ -9596,9 +9655,14 @@ const getQueryUsersUrl = () => {
9596
9655
  /**
9597
9656
  * Returns the users of a project, paginated with a cursor.
9598
9657
 
9599
- The project comes from the credential, not from a parameter: the operation
9600
- is bound to the token's own project by construction. This is why it takes
9601
- no `project_id`, unlike the other query endpoints.
9658
+ The project comes from the credential, not from a parameter: the
9659
+ operation is bound to the credential's home project by construction
9660
+ (oauth2 secret or user-bound session). This is why it takes no
9661
+ `project_id`, unlike the other query endpoints.
9662
+
9663
+ Accepts either a project secret (`oauth2`) or a user-bound Console
9664
+ session cookie (`nextgenSession`). CSRF/Origin for cookie mutations
9665
+ is a follow-up (#1140).
9602
9666
 
9603
9667
  * @summary Query users
9604
9668
  */
@@ -9630,6 +9694,28 @@ const getUserByID = async (userId, params, options) => {
9630
9694
  method: "GET"
9631
9695
  });
9632
9696
  };
9697
+ const getPatchUserByIDUrl = (userId) => {
9698
+ return `${getProxyPath()}/users/${userId}`;
9699
+ };
9700
+ /**
9701
+ * Partially updates a user's schema-defined attributes, and optionally
9702
+ moves the user to another registered schema. The merged result is
9703
+ validated against the schema before commit. Concurrent writes are
9704
+ last-write-wins.
9705
+
9706
+ * @summary Update user by ID
9707
+ */
9708
+ const patchUserByID = async (userId, patchUserByIDBody, options) => {
9709
+ return customFetch(getPatchUserByIDUrl(userId), {
9710
+ ...options,
9711
+ method: "PATCH",
9712
+ headers: {
9713
+ "Content-Type": "application/json",
9714
+ ...options?.headers
9715
+ },
9716
+ body: JSON.stringify(patchUserByIDBody)
9717
+ });
9718
+ };
9633
9719
  const getDeleteUserByIDUrl = (userId) => {
9634
9720
  return `${getProxyPath()}/users/${userId}`;
9635
9721
  };
@@ -9760,6 +9846,52 @@ const getMyUser = async (options) => {
9760
9846
  method: "GET"
9761
9847
  });
9762
9848
  };
9849
+ const getPatchMyUserUrl = () => {
9850
+ return `${getProxyPath()}/users/me`;
9851
+ };
9852
+ /**
9853
+ * Partially updates the caller's own schema-defined attributes. The merged
9854
+ result is validated against the user's schema before commit. Concurrent
9855
+ writes are last-write-wins.
9856
+
9857
+ * @summary Update my user information
9858
+ */
9859
+ const patchMyUser = async (patchMyUserBody, options) => {
9860
+ return customFetch(getPatchMyUserUrl(), {
9861
+ ...options,
9862
+ method: "PATCH",
9863
+ headers: {
9864
+ "Content-Type": "application/json",
9865
+ ...options?.headers
9866
+ },
9867
+ body: JSON.stringify(patchMyUserBody)
9868
+ });
9869
+ };
9870
+ const getListMyProjectsUrl = (params) => {
9871
+ const normalizedParams = new URLSearchParams();
9872
+ Object.entries(params || {}).forEach(([key, value]) => {
9873
+ if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
9874
+ });
9875
+ const stringifiedParams = normalizedParams.toString();
9876
+ return stringifiedParams.length > 0 ? `${getProxyPath()}/users/me/projects?${stringifiedParams}` : `${getProxyPath()}/users/me/projects`;
9877
+ };
9878
+ /**
9879
+ * The projects the signed-in user holds an active grant on, either directly or
9880
+ through a team they are a member of. Ordered by project id and paged with an
9881
+ opaque cursor.
9882
+
9883
+ This is not the same question as `queryProjects`, which answers with the one
9884
+ project the calling credential is bound to. Here the session is the caller
9885
+ and the grants are the answer, so the list spans projects.
9886
+
9887
+ * @summary List the projects I can act on
9888
+ */
9889
+ const listMyProjects = async (params, options) => {
9890
+ return customFetch(getListMyProjectsUrl(params), {
9891
+ ...options,
9892
+ method: "GET"
9893
+ });
9894
+ };
9763
9895
  const getCreateFlowUrl = () => {
9764
9896
  return `${getProxyPath()}/flow`;
9765
9897
  };
@@ -10445,42 +10577,6 @@ const getFlowDefinition = async (id, options) => {
10445
10577
  method: "GET"
10446
10578
  });
10447
10579
  };
10448
- const getUpdateFlowDefinitionUrl = (id) => {
10449
- return `${getProxyPath()}/flow_definitions/${id}`;
10450
- };
10451
- /**
10452
- * Update a flow definition by id. This endpoint replaces the existing flow definition.
10453
- If `flow_definition.status` is omitted, the current status is preserved
10454
-
10455
- * @summary Update a flow definition by id
10456
- */
10457
- const updateFlowDefinition = async (id, updateFlowDefinitionBody, options) => {
10458
- return customFetch(getUpdateFlowDefinitionUrl(id), {
10459
- ...options,
10460
- method: "PUT",
10461
- headers: {
10462
- "Content-Type": "application/json",
10463
- ...options?.headers
10464
- },
10465
- body: JSON.stringify(updateFlowDefinitionBody)
10466
- });
10467
- };
10468
- const getDeleteFlowDefinitionUrl = (id) => {
10469
- return `${getProxyPath()}/flow_definitions/${id}`;
10470
- };
10471
- /**
10472
- * Delete a flow definition by id.
10473
- If the flow definition is currently being used by a flow, the deletion will fail.
10474
- If the flow definition is the last active flow definition for a given purpose, the deletion will fail to prevent disruption of new flows being started for that purpose.
10475
-
10476
- * @summary Delete a flow definition by id
10477
- */
10478
- const deleteFlowDefinition = async (id, options) => {
10479
- return customFetch(getDeleteFlowDefinitionUrl(id), {
10480
- ...options,
10481
- method: "DELETE"
10482
- });
10483
- };
10484
10580
  const getCreateTeamUrl = (params) => {
10485
10581
  const normalizedParams = new URLSearchParams();
10486
10582
  Object.entries(params || {}).forEach(([key, value]) => {
@@ -10554,6 +10650,11 @@ users whose lifecycle it owns are deactivated with it.
10554
10650
  The request is idempotent. Deleting a team that is already deactivated
10555
10651
  or doesn't exist succeeds without changing anything.
10556
10652
 
10653
+ A team that still owns a project is refused with `409
10654
+ team.owns_project`: deactivating it would leave the project owned by a
10655
+ dead team and so unmanageable. No endpoint releases that ownership yet,
10656
+ so such a team cannot currently be deactivated through the API.
10657
+
10557
10658
  * @summary Delete team
10558
10659
  */
10559
10660
  const deleteTeam = async (teamId, options) => {
@@ -10633,8 +10734,7 @@ const getCreateBrandingUrl = (params) => {
10633
10734
  /**
10634
10735
  * Publishes a new immutable branding revision for the project. Branding
10635
10736
  revisions cannot be updated or deleted; every edit publishes a new
10636
- revision, and flow responses resolve the latest revision per project
10637
- (see ADR 040).
10737
+ revision, and flow responses resolve the latest revision per project.
10638
10738
 
10639
10739
  The `liquid_template` is validated lexically on save (size, encoding,
10640
10740
  banned patterns such as `<script>` tags, inline event handlers, and the
@@ -10665,9 +10765,9 @@ const getListBrandingUrl = (params) => {
10665
10765
  /**
10666
10766
  * Lists branding revisions for the project, newest first, capped at the
10667
10767
  100 most recent. The first entry is the revision flow responses
10668
- currently resolve. Deliberately unpaginated in v1 — list endpoints
10669
- gain a real query mechanism together (ADR 031); advertising pagination
10670
- parameters the server ignores would be worse than none.
10768
+ currently resolve. Deliberately unpaginated in v1 — list endpoints gain a
10769
+ real query mechanism together; advertising pagination parameters the
10770
+ server ignores would be worse than none.
10671
10771
 
10672
10772
  * @summary List branding revisions
10673
10773
  */
@@ -10690,6 +10790,142 @@ const getBrandingById = async (id, options) => {
10690
10790
  method: "GET"
10691
10791
  });
10692
10792
  };
10793
+ const getCreateIdpUrl = (params) => {
10794
+ const normalizedParams = new URLSearchParams();
10795
+ Object.entries(params || {}).forEach(([key, value]) => {
10796
+ if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
10797
+ });
10798
+ const stringifiedParams = normalizedParams.toString();
10799
+ return stringifiedParams.length > 0 ? `${getProxyPath()}/idps?${stringifiedParams}` : `${getProxyPath()}/idps`;
10800
+ };
10801
+ /**
10802
+ * Creates or revises a connection document. If the slug does not already
10803
+ exist, a new connection is created. If a connection with that slug already
10804
+ exists, a revision is created.
10805
+
10806
+ A revision gets a new `revision_id` and does not modify the connection
10807
+ `id`, so identity links that reference the connection keep resolving while
10808
+ releases and auth attempts stay pinned to the revision they captured.
10809
+
10810
+ Identity fields are fixed for the life of the connection and a revision
10811
+ that changes one is rejected: `protocol`, `subject_claim`, and the
10812
+ endpoints that name the authority (`issuer` for OIDC, `token_endpoint`
10813
+ and `userinfo_endpoint` for OAuth 2.0). Their values decide which provider
10814
+ account a stored subject belongs to, so changing one would silently
10815
+ repoint existing identities at a different provider.
10816
+
10817
+ `subject_claim` is optional for OIDC and required for OAuth 2.0. For an
10818
+ OIDC connection, `sub` is used as the default value. If a revision updates
10819
+ `subject_claim` to a different value than the one set during creation, the
10820
+ request is rejected.
10821
+
10822
+ The document is validated against the `idp-connection.json` schema before
10823
+ anything is stored.
10824
+
10825
+ * @summary Create or revise an identity provider connection
10826
+ */
10827
+ const createIdp = async (createIdpBody, params, options) => {
10828
+ return customFetch(getCreateIdpUrl(params), {
10829
+ ...options,
10830
+ method: "POST",
10831
+ headers: {
10832
+ "Content-Type": "application/json",
10833
+ ...options?.headers
10834
+ },
10835
+ body: JSON.stringify(createIdpBody)
10836
+ });
10837
+ };
10838
+ const getQueryIdpsUrl = (params) => {
10839
+ const normalizedParams = new URLSearchParams();
10840
+ Object.entries(params || {}).forEach(([key, value]) => {
10841
+ if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
10842
+ });
10843
+ const stringifiedParams = normalizedParams.toString();
10844
+ return stringifiedParams.length > 0 ? `${getProxyPath()}/idps/query?${stringifiedParams}` : `${getProxyPath()}/idps/query`;
10845
+ };
10846
+ /**
10847
+ * Returns the identity provider connections of a project, paginated with a
10848
+ cursor. One row per connection, carrying its newest revision.
10849
+
10850
+ * @summary Query identity provider connections
10851
+ */
10852
+ const queryIdps = async (queryIdpsBody, params, options) => {
10853
+ return customFetch(getQueryIdpsUrl(params), {
10854
+ ...options,
10855
+ method: "POST",
10856
+ headers: {
10857
+ "Content-Type": "application/json",
10858
+ ...options?.headers
10859
+ },
10860
+ body: JSON.stringify(queryIdpsBody)
10861
+ });
10862
+ };
10863
+ const getGetIdpByIdUrl = (id, params) => {
10864
+ const normalizedParams = new URLSearchParams();
10865
+ Object.entries(params || {}).forEach(([key, value]) => {
10866
+ if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
10867
+ });
10868
+ const stringifiedParams = normalizedParams.toString();
10869
+ return stringifiedParams.length > 0 ? `${getProxyPath()}/idps/${id}?${stringifiedParams}` : `${getProxyPath()}/idps/${id}`;
10870
+ };
10871
+ /**
10872
+ * Reads a connection by its id, at its newest revision.
10873
+ `GET /idps/{id}/revisions` lists every revision of the connection.
10874
+
10875
+ The lookup is scoped to the project in `project_id`.
10876
+
10877
+ * @summary Get an identity provider connection by id
10878
+ */
10879
+ const getIdpById = async (id, params, options) => {
10880
+ return customFetch(getGetIdpByIdUrl(id, params), {
10881
+ ...options,
10882
+ method: "GET"
10883
+ });
10884
+ };
10885
+ const getListIdpRevisionsUrl = (id, params) => {
10886
+ const normalizedParams = new URLSearchParams();
10887
+ Object.entries(params || {}).forEach(([key, value]) => {
10888
+ if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
10889
+ });
10890
+ const stringifiedParams = normalizedParams.toString();
10891
+ return stringifiedParams.length > 0 ? `${getProxyPath()}/idps/${id}/revisions?${stringifiedParams}` : `${getProxyPath()}/idps/${id}/revisions`;
10892
+ };
10893
+ /**
10894
+ * Returns every revision of one connection, newest first, paginated with a
10895
+ cursor. The order is fixed, so there is no `sorting`.
10896
+
10897
+ The lookup is scoped to the project in `project_id`.
10898
+
10899
+ * @summary List the revisions of an identity provider connection
10900
+ */
10901
+ const listIdpRevisions = async (id, params, options) => {
10902
+ return customFetch(getListIdpRevisionsUrl(id, params), {
10903
+ ...options,
10904
+ method: "GET"
10905
+ });
10906
+ };
10907
+ const getGetIdpRevisionByIdUrl = (revisionId, params) => {
10908
+ const normalizedParams = new URLSearchParams();
10909
+ Object.entries(params || {}).forEach(([key, value]) => {
10910
+ if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
10911
+ });
10912
+ const stringifiedParams = normalizedParams.toString();
10913
+ return stringifiedParams.length > 0 ? `${getProxyPath()}/idps/revisions/${revisionId}?${stringifiedParams}` : `${getProxyPath()}/idps/revisions/${revisionId}`;
10914
+ };
10915
+ /**
10916
+ * Reads one revision of a connection by its `revision_id`, the value an auth
10917
+ attempt or a release pins.
10918
+
10919
+ The lookup is scoped to the project in `project_id`.
10920
+
10921
+ * @summary Get one revision of an identity provider connection
10922
+ */
10923
+ const getIdpRevisionById = async (revisionId, params, options) => {
10924
+ return customFetch(getGetIdpRevisionByIdUrl(revisionId, params), {
10925
+ ...options,
10926
+ method: "GET"
10927
+ });
10928
+ };
10693
10929
  const getCreateReleaseUrl = (params) => {
10694
10930
  const normalizedParams = new URLSearchParams();
10695
10931
  Object.entries(params || {}).forEach(([key, value]) => {
@@ -10789,10 +11025,23 @@ const getCreateGrantUrl = (params) => {
10789
11025
  /**
10790
11026
  * Bind a user or team to `project.viewer`, `project.editor`, or
10791
11027
  `project.admin` on the project identified by the `project-id` header.
10792
- IDs are `asgn_<opaque>`. Owning-team (`project.team`) grants are not
10793
- created here — claim owns that path. An unrevoked grant with the same
10794
- principal and relation occupies the unique key even after `expires_at`;
10795
- DELETE it before re-creating.
11028
+ Name the principal with `user` (`user_id` or `identifier`) or `team`
11029
+ (`team_id` or `name`). IDs are `asgn_<opaque>`. Owning-team
11030
+ (`project.team`) grants are not created here — claim owns that path. An
11031
+ unrevoked grant with the same principal and relation occupies the unique
11032
+ key even after `expires_at`; DELETE it before re-creating.
11033
+ Create does not accept `expand`. The `user_id` and team locators
11034
+ return 201, whose `user` / `team` carry only `user_id` / `team_id`,
11035
+ and still 404 / 409 when the principal is missing or the tuple
11036
+ already exists. Creating by `user.identifier` answers 202 with no
11037
+ body on every outcome. Granting the session caller's own user is
11038
+ `grant.invalid` on either user locator. Read the grant back for
11039
+ identifier and display.
11040
+
11041
+ Accepts either a project secret (`oauth2`) or a user-bound Console
11042
+ session cookie (`nextgenSession`). Session callers are authorized as
11043
+ the human against the target project (home may differ). CSRF/Origin
11044
+ for cookie mutations is a follow-up (#1140).
10796
11045
 
10797
11046
  * @summary Create grant
10798
11047
  */
@@ -10822,10 +11071,16 @@ viewer, editor, or admin), including expired grants so a client can
10822
11071
  DELETE before re-granting. Project-secret setup (`sk_proj`) and
10823
11072
  owning-team (`relation=team`) rows are not returned. Grants are not in
10824
11073
  `resource_scope_index`; project scope is required on the query (same as
10825
- get). Requires `project.read`. `expand: ["principal"]` additionally
10826
- requires `user.read` and `team.read` (documented on the expand enum;
10827
- those scopes cannot be ANDed onto this security block because they are
10828
- body-conditional).
11074
+ get). Requires `project.read`. `expand: ["principal"]` adds envelope
11075
+ fields on `user` / `team` and additionally requires `user.read` and
11076
+ `team.read` for project secrets (documented on the expand enum; those
11077
+ scopes cannot be ANDed onto this security block because they are
11078
+ body-conditional). A user-bound Console session that already passed
11079
+ the project Check may expand without those scopes.
11080
+
11081
+ Accepts either a project secret (`oauth2`) or a user-bound Console
11082
+ session cookie (`nextgenSession`). CSRF/Origin for cookie mutations
11083
+ is a follow-up (#1140).
10829
11084
 
10830
11085
  * @summary Query grants
10831
11086
  */
@@ -10857,6 +11112,14 @@ still ignores expired grants. Grants are not registered in
10857
11112
  `resource_scope_index`; project scope is required on the query (same as
10858
11113
  events). Misses, revoked rows, project-secret setup (`sk_proj`),
10859
11114
  owning-team (`relation=team`) rows, and cross-project ids return 404.
11115
+ `expand=principal` adds envelope fields on `user` or `team` and requires
11116
+ `user.read` and `team.read` in addition to `project.read` for project
11117
+ secrets. A user-bound Console session that already passed the project
11118
+ Check may expand without those scopes.
11119
+
11120
+ Accepts either a project secret (`oauth2`) or a user-bound Console
11121
+ session cookie (`nextgenSession`). CSRF/Origin for cookie mutations
11122
+ is a follow-up (#1140).
10860
11123
 
10861
11124
  * @summary Get grant
10862
11125
  */
@@ -10880,6 +11143,10 @@ Already-revoked, missing, project-secret setup, and owning-team grants
10880
11143
  return 404. The row is not un-revoked. Expired grants can still be
10881
11144
  revoked so the unique binding can be reused.
10882
11145
 
11146
+ Accepts either a project secret (`oauth2`) or a user-bound Console
11147
+ session cookie (`nextgenSession`). CSRF/Origin for cookie mutations
11148
+ is a follow-up (#1140).
11149
+
10883
11150
  * @summary Revoke grant
10884
11151
  */
10885
11152
  const deleteGrant = async (id, params, options) => {
@@ -10946,6 +11213,122 @@ const getEvent = async (id, params, options) => {
10946
11213
  method: "GET"
10947
11214
  });
10948
11215
  };
11216
+ const getGetVariablesUrl = (params) => {
11217
+ const normalizedParams = new URLSearchParams();
11218
+ Object.entries(params || {}).forEach(([key, value]) => {
11219
+ if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
11220
+ });
11221
+ const stringifiedParams = normalizedParams.toString();
11222
+ return stringifiedParams.length > 0 ? `${getProxyPath()}/variables?${stringifiedParams}` : `${getProxyPath()}/variables`;
11223
+ };
11224
+ /**
11225
+ * Returns the variables entered at the owner this request addresses, keyed by
11226
+ name — one environment of the project with `environment_name`, the project
11227
+ level itself without it.
11228
+
11229
+ Owners are separate, not a ladder: an environment does not inherit the
11230
+ project's variables and the project does not see its environments'. Reading
11231
+ everything a project holds therefore means reading each owner in turn.
11232
+
11233
+ Secret values are not returned. A secret appears as `{"secret": true}`,
11234
+ which says a value is held without disclosing it.
11235
+
11236
+ * @summary Get variables
11237
+ */
11238
+ const getVariables = async (params, options) => {
11239
+ return customFetch(getGetVariablesUrl(params), {
11240
+ ...options,
11241
+ method: "GET"
11242
+ });
11243
+ };
11244
+ const getUpdateVariablesUrl = (params) => {
11245
+ const normalizedParams = new URLSearchParams();
11246
+ Object.entries(params || {}).forEach(([key, value]) => {
11247
+ if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
11248
+ });
11249
+ const stringifiedParams = normalizedParams.toString();
11250
+ return stringifiedParams.length > 0 ? `${getProxyPath()}/variables?${stringifiedParams}` : `${getProxyPath()}/variables`;
11251
+ };
11252
+ /**
11253
+ * Enters, replaces and removes variables at the owner this request
11254
+ addresses.
11255
+
11256
+ Every name in the body is applied at exactly that owner — the project, or
11257
+ the environment named by `environment_name` — and reaches no other. Names
11258
+ not in the body are untouched.
11259
+
11260
+ A bare scalar enters a non-secret value. `{"value": …, "secret": true}`
11261
+ stores the value encrypted under the project's active `secret` key, after
11262
+ which it can be referenced but not read back. `null` removes the name from
11263
+ this owner (RFC 7386), which is how several variables are removed in one
11264
+ request; removing a name this owner does not hold is a no-op rather than an
11265
+ error.
11266
+
11267
+ The body is applied whole or not at all, so a rejected request leaves the
11268
+ owner exactly as it was. Writing the same name and owner twice replaces the
11269
+ value rather than duplicating it, which makes a retry safe.
11270
+
11271
+ * @summary Update variables
11272
+ */
11273
+ const updateVariables = async (updateVariablesBody, params, options) => {
11274
+ return customFetch(getUpdateVariablesUrl(params), {
11275
+ ...options,
11276
+ method: "PATCH",
11277
+ headers: {
11278
+ "Content-Type": "application/json",
11279
+ ...options?.headers
11280
+ },
11281
+ body: JSON.stringify(updateVariablesBody)
11282
+ });
11283
+ };
11284
+ const getGetVariableUrl = (variableName, params) => {
11285
+ const normalizedParams = new URLSearchParams();
11286
+ Object.entries(params || {}).forEach(([key, value]) => {
11287
+ if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
11288
+ });
11289
+ const stringifiedParams = normalizedParams.toString();
11290
+ return stringifiedParams.length > 0 ? `${getProxyPath()}/variables/${variableName}?${stringifiedParams}` : `${getProxyPath()}/variables/${variableName}`;
11291
+ };
11292
+ /**
11293
+ * Reads one variable by name from the owner this request addresses — one
11294
+ environment of the project with `environment_name`, the project level
11295
+ itself without it.
11296
+
11297
+ A name that owner has not entered answers `var.not_found`, even when
11298
+ another owner of the same project holds it: nothing is inherited. A secret
11299
+ is found but not disclosed: the response is `{"secret": true}`.
11300
+
11301
+ * @summary Get a variable
11302
+ */
11303
+ const getVariable = async (variableName, params, options) => {
11304
+ return customFetch(getGetVariableUrl(variableName, params), {
11305
+ ...options,
11306
+ method: "GET"
11307
+ });
11308
+ };
11309
+ const getDeleteVariableUrl = (variableName, params) => {
11310
+ const normalizedParams = new URLSearchParams();
11311
+ Object.entries(params || {}).forEach(([key, value]) => {
11312
+ if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
11313
+ });
11314
+ const stringifiedParams = normalizedParams.toString();
11315
+ return stringifiedParams.length > 0 ? `${getProxyPath()}/variables/${variableName}?${stringifiedParams}` : `${getProxyPath()}/variables/${variableName}`;
11316
+ };
11317
+ /**
11318
+ * Removes the variable this owner entered under this name.
11319
+
11320
+ A variable is deletable only by the owner that entered it. Deleting a name
11321
+ another owner of the same project holds answers `var.not_found` and leaves
11322
+ that owner's value standing.
11323
+
11324
+ * @summary Delete a variable
11325
+ */
11326
+ const deleteVariable = async (variableName, params, options) => {
11327
+ return customFetch(getDeleteVariableUrl(variableName, params), {
11328
+ ...options,
11329
+ method: "DELETE"
11330
+ });
11331
+ };
10949
11332
  //#endregion
10950
11333
  //#region ../api/dist/runtime/api-factory.mjs
10951
11334
  /**
@@ -13489,9 +13872,10 @@ function createSanitiser() {
13489
13872
  * the `Branding` JSON into a CSSStyleSheet of `:host { --zl-* }` declarations
13490
13873
  * and applies it via `shadowRoot.adoptedStyleSheets`.
13491
13874
  *
13492
- * Dark-mode overrides are emitted as `:host([data-theme="dark"]) { ... }`
13493
- * (matching `docs/design/branding/tokens.md`). Resolution between
13494
- * `light | dark | auto` happens in `<zitadel-login>`.
13875
+ * Light and dark are independent surfaces: each side's palette is emitted under
13876
+ * its own `data-theme` selector only, so a key the revision left unset on one
13877
+ * side takes the maintained default for that side rather than the other side's
13878
+ * colour. Resolution between `light | dark | auto` happens in `<zitadel-login>`.
13495
13879
  *
13496
13880
  * Base layer: the design-tokens package ships the full `--zl-*` set as both a
13497
13881
  * `.css` file (for host pages) and a `tokensCss` string (for shadow roots and
@@ -13499,13 +13883,32 @@ function createSanitiser() {
13499
13883
  * paint correctly even when the host page didn't `@import` tokens.css — the
13500
13884
  * orchestrator is meant to drop into any page.
13501
13885
  */
13502
- const RADIUS_MAP = {
13503
- none: "0",
13504
- sm: "0.25rem",
13505
- md: "0.5rem",
13506
- lg: "0.75rem",
13507
- full: "9999px"
13508
- };
13886
+ const RADIUS_STEPS = [
13887
+ "xs",
13888
+ "sm",
13889
+ "md",
13890
+ "lg",
13891
+ "xl"
13892
+ ];
13893
+ const RADIUS_RATIOS = (() => {
13894
+ const base = remToNumber(tokens.radius.md);
13895
+ const ratios = {};
13896
+ for (const step of RADIUS_STEPS) ratios[step] = remToNumber(tokens.radius[step]) / base;
13897
+ return ratios;
13898
+ })();
13899
+ const RADIUS_PRESETS = {
13900
+ none: 0,
13901
+ sm: .25,
13902
+ md: .5,
13903
+ lg: .75
13904
+ };
13905
+ const TEXT_STEPS = [
13906
+ "xs",
13907
+ "sm",
13908
+ "base",
13909
+ "lg",
13910
+ "xl"
13911
+ ];
13509
13912
  const DENSITY_MAP = {
13510
13913
  compact: {
13511
13914
  "--zl-spacing-4": "0.75rem",
@@ -13557,34 +13960,35 @@ const PALETTE_MAP = {
13557
13960
  * the moment before a theme resolves.
13558
13961
  */
13559
13962
  const BRANDING_SELECTOR = ":host, :host([data-theme=\"light\"]), :host([data-theme=\"dark\"])";
13560
- /** Dark-only overrides. Emitted after {@link BRANDING_SELECTOR}, so it wins on order. */
13963
+ /** Per-side palettes. Emitted after {@link BRANDING_SELECTOR}, so they win on order. */
13964
+ const LIGHT_SELECTOR = ":host([data-theme=\"light\"])";
13561
13965
  const DARK_SELECTOR = ":host([data-theme=\"dark\"])";
13562
13966
  /**
13563
13967
  * Build a CSS string of `:host { --zl-* }` declarations from a Branding
13564
- * payload. Light values land on `:host`; dark overrides land on
13565
- * `:host([data-theme="dark"])`.
13566
- */
13567
- function buildBrandingStylesheet(branding, options = {}) {
13568
- const lightDecls = collectDeclarations(branding);
13569
- const darkPalette = branding?.theme?.dark?.palette;
13570
- const darkDecls = darkPalette ? mapPalette(darkPalette) : {};
13968
+ * payload. Shape and typography are shared across the sides and land on the
13969
+ * shared selector; each side's palette lands only under its own `data-theme`
13970
+ * selector, and nowhere else.
13971
+ *
13972
+ * No copy of the resolved side goes on the shared selector. That copy would
13973
+ * reach `[data-theme="light"]` as well, so a key set on the dark side and not
13974
+ * the light one would paint on both, with nothing in the light block to
13975
+ * override it. Callers stamp `data-theme` on the host — `applySurfaceTheme`
13976
+ * does it in the same update that adopts this sheet — so the per-side block is
13977
+ * already matching by the time anything paints.
13978
+ */
13979
+ function buildBrandingStylesheet(branding) {
13980
+ const shared = collectDeclarations(branding);
13981
+ const light = mapPalette(branding?.theme?.light?.palette);
13982
+ const dark = mapPalette(branding?.theme?.dark?.palette);
13571
13983
  const blocks = [];
13572
- if (Object.keys(lightDecls).length > 0) blocks.push(formatBlock(BRANDING_SELECTOR, lightDecls));
13573
- if (Object.keys(darkDecls).length > 0) blocks.push(formatBlock(DARK_SELECTOR, darkDecls));
13574
- if (options.resolvedTheme === "dark" && darkPalette) {
13575
- const merged = {
13576
- ...lightDecls,
13577
- ...darkDecls
13578
- };
13579
- blocks.length = 0;
13580
- blocks.push(formatBlock(BRANDING_SELECTOR, merged));
13581
- }
13984
+ if (Object.keys(shared).length > 0) blocks.push(formatBlock(BRANDING_SELECTOR, shared));
13985
+ if (Object.keys(light).length > 0) blocks.push(formatBlock(LIGHT_SELECTOR, light));
13986
+ if (Object.keys(dark).length > 0) blocks.push(formatBlock(DARK_SELECTOR, dark));
13582
13987
  return blocks.join("\n");
13583
13988
  }
13584
13989
  function collectDeclarations(branding) {
13585
13990
  if (!branding) return {};
13586
13991
  const decls = {};
13587
- Object.assign(decls, mapPalette(branding.palette));
13588
13992
  Object.assign(decls, mapTypography(branding.typography));
13589
13993
  Object.assign(decls, mapShape(branding.shape));
13590
13994
  return decls;
@@ -13605,24 +14009,52 @@ function mapTypography(typography) {
13605
14009
  out["--zl-font-family-sans"] = typography.font_family;
13606
14010
  out["--zl-font-family-heading"] = typography.font_family;
13607
14011
  }
13608
- if (typography.font_family_heading) out["--zl-font-family-heading"] = typography.font_family_heading;
13609
- if (typography.font_family_mono) out["--zl-font-family-mono"] = typography.font_family_mono;
13610
14012
  const scale = clamp(typography.scale ?? 1, .75, 1.25);
13611
- if (scale !== 1) out["--zl-font-scale"] = `${scale}`;
14013
+ if (scale !== 1) for (const step of TEXT_STEPS) {
14014
+ out[`--zl-text-${step}-size`] = scaleRem(tokens.text[step].size, scale);
14015
+ out[`--zl-text-${step}-leading`] = scaleRem(tokens.text[step].leading, scale);
14016
+ }
13612
14017
  return out;
13613
14018
  }
13614
14019
  function mapShape(shape) {
13615
14020
  if (!shape) return {};
13616
14021
  const out = {};
13617
- if (shape.radius && RADIUS_MAP[shape.radius]) {
13618
- const radius = RADIUS_MAP[shape.radius];
13619
- out["--zl-radius-md"] = radius;
13620
- out["--zl-radius-lg"] = radius === "0" ? "0" : `calc(${radius} * 1.25)`;
13621
- out["--zl-radius-xl"] = radius === "0" ? "0" : `calc(${radius} * 1.75)`;
13622
- }
14022
+ Object.assign(out, mapRadius(shape.radius));
13623
14023
  if (shape.density) Object.assign(out, DENSITY_MAP[shape.density]);
14024
+ const logoScale = shape.logo_scale;
14025
+ if (typeof logoScale === "number" && Number.isFinite(logoScale)) out["--zl-logo-scale"] = `${clamp(logoScale, .5, 2)}`;
14026
+ return out;
14027
+ }
14028
+ /**
14029
+ * A brand picks one corner value and the whole ramp follows it in proportion,
14030
+ * which is what keeps the card rounder than the controls inside it. Both forms
14031
+ * the revision accepts land here: a preset name, or an integer number of pixels
14032
+ * for the brand that has a specific value.
14033
+ */
14034
+ function mapRadius(radius) {
14035
+ if (radius == null) return {};
14036
+ const out = {};
14037
+ if (radius === "full") {
14038
+ for (const step of RADIUS_STEPS) out[`--zl-radius-${step}`] = tokens.radius.full;
14039
+ return out;
14040
+ }
14041
+ const [base, unit] = typeof radius === "number" ? [radius, "px"] : radius in RADIUS_PRESETS ? [RADIUS_PRESETS[radius], "rem"] : [NaN, ""];
14042
+ if (Number.isNaN(base)) return {};
14043
+ for (const step of RADIUS_STEPS) {
14044
+ const value = base * RADIUS_RATIOS[step];
14045
+ out[`--zl-radius-${step}`] = value === 0 ? "0" : `${round(value)}${unit}`;
14046
+ }
13624
14047
  return out;
13625
14048
  }
14049
+ function scaleRem(value, scale) {
14050
+ return `${round(remToNumber(value) * scale)}rem`;
14051
+ }
14052
+ function remToNumber(value) {
14053
+ return Number.parseFloat(value);
14054
+ }
14055
+ function round(value) {
14056
+ return Math.round(value * 1e4) / 1e4;
14057
+ }
13626
14058
  function formatBlock(selector, decls) {
13627
14059
  return `${selector} {\n${Object.entries(decls).map(([prop, value]) => ` ${prop}: ${value};`).join("\n")}\n}`;
13628
14060
  }
@@ -13662,8 +14094,8 @@ function applyBaseTokens(shadowRoot) {
13662
14094
  * Callers should run `applyBaseTokens(shadowRoot)` first (once per shadow
13663
14095
  * root) so the base values exist before branding patches them.
13664
14096
  */
13665
- function applyBrandingTokens(shadowRoot, branding, resolvedTheme) {
13666
- const css = buildBrandingStylesheet(branding, { resolvedTheme });
14097
+ function applyBrandingTokens(shadowRoot, branding) {
14098
+ const css = buildBrandingStylesheet(branding);
13667
14099
  if (typeof CSSStyleSheet === "undefined") return;
13668
14100
  const sheet = new CSSStyleSheet();
13669
14101
  sheet.replaceSync(css);
@@ -13678,9 +14110,11 @@ function applyBrandingTokens(shadowRoot, branding, resolvedTheme) {
13678
14110
  * and a variant-derived fallback on top of the same branding input.
13679
14111
  *
13680
14112
  * Defaults to dark: the design system's primary surface, and the mode a
13681
- * hosted login page renders when a tenant states no preference.
14113
+ * hosted login page renders when a revision states no preference.
13682
14114
  */
13683
14115
  function resolveTheme(branding) {
14116
+ const [only, second] = publishedSides(branding);
14117
+ if (only && !second) return only;
13684
14118
  const mode = branding?.theme?.mode ?? "dark";
13685
14119
  if (mode === "light") return "light";
13686
14120
  if (mode === "dark") return "dark";
@@ -13698,7 +14132,7 @@ function resolveTheme(branding) {
13698
14132
  * (Arimo, named first in `--zl-font-family-sans`). The component ships this
13699
14133
  * so the auth screens render in the brand font out of the box, even when the
13700
14134
  * server returns no branding. This is the "default users can override" layer.
13701
- * - The **tenant override** (`applyFontUrl`) — `branding.font_url`, resolved
14135
+ * - The **tenant override** (`applyFontUrl`) — `branding.typography.font_url`, resolved
13702
14136
  * server-side from the app → team → project branding hierarchy.
13703
14137
  *
13704
14138
  * Both must live at document level, not inside the shadow root: browsers ignore
@@ -13727,7 +14161,7 @@ const DEFAULT_LINK_ID = "zl-default-font-link";
13727
14161
  * Design-system default brand font. Arimo is the Figma body face and leads the
13728
14162
  * `--zl-font-family-sans` stack; loading it here makes the unbranded auth UI
13729
14163
  * paint in the brand font instead of the system fallback. Tenants override it
13730
- * with `branding.font_url`. Exported so a privacy- or offline-sensitive
14164
+ * with `branding.typography.font_url`. Exported so a privacy- or offline-sensitive
13731
14165
  * deployment can self-host the same face and swap this single URL.
13732
14166
  */
13733
14167
  const DEFAULT_BRAND_FONT_HREF = "https://fonts.googleapis.com/css2?family=Arimo:ital,wght@0,400;0,500;0,600;0,700;1,400&display=swap";
@@ -13747,7 +14181,7 @@ const DEFAULT_BRAND_FONT_HREF = "https://fonts.googleapis.com/css2?family=Arimo:
13747
14181
  /** Per-document, per-link-id map of each owner's desired href. */
13748
14182
  const registries = /* @__PURE__ */ new WeakMap();
13749
14183
  /**
13750
- * Inject the tenant's `branding.font_url` stylesheet. Pass `null`/`undefined`
14184
+ * Inject the tenant's `branding.typography.font_url` stylesheet. Pass `null`/`undefined`
13751
14185
  * to withdraw this surface's registration (e.g. when a flow resolves with no
13752
14186
  * tenant font); the link disappears once no surface registers a URL.
13753
14187
  */
@@ -13757,7 +14191,7 @@ function applyFontUrl(shadowRoot, fontUrl) {
13757
14191
  /**
13758
14192
  * Inject the design-system default font stylesheet. Defaults to
13759
14193
  * {@link DEFAULT_BRAND_FONT_HREF}; pass `null` to withdraw this surface's
13760
- * registration (the orchestrator does this when a tenant `font_url` takes
14194
+ * registration (the orchestrator does this when a tenant font stylesheet takes
13761
14195
  * over, to avoid a redundant request).
13762
14196
  */
13763
14197
  function applyDefaultFont(shadowRoot, href = DEFAULT_BRAND_FONT_HREF) {
@@ -13824,7 +14258,10 @@ var ThemeController = class {
13824
14258
  * element nor the branding payload names a mode. Precedence, strongest
13825
14259
  * first: element `theme` property → `branding.theme.mode` → fallback. The
13826
14260
  * element wins because the page embedding the widget knows its own surface
13827
- * better than the tenant's stored branding does.
14261
+ * better than the tenant's stored branding does — but it selects among the
14262
+ * sides the revision published, and cannot reach one it did not. A revision
14263
+ * with a single published side therefore ignores this property: an embedder
14264
+ * pinning `theme="light"` against a dark-only revision gets dark.
13828
14265
  */
13829
14266
  setModePreference(explicit, fallback) {
13830
14267
  if (this.explicitMode === explicit && this.fallbackMode === fallback) return;
@@ -13839,6 +14276,12 @@ var ThemeController = class {
13839
14276
  this.detach();
13840
14277
  }
13841
14278
  refresh() {
14279
+ const [only, second] = publishedSides(this.branding);
14280
+ if (only && !second) {
14281
+ this.detach();
14282
+ this.update(only);
14283
+ return;
14284
+ }
13842
14285
  const mode = this.explicitMode ?? this.branding?.theme?.mode ?? this.fallbackMode;
13843
14286
  if (mode === "auto") {
13844
14287
  this.attach();
@@ -13917,9 +14360,10 @@ var ZitadelSurface = class extends i$2 {
13917
14360
  const root = this.shadowRoot;
13918
14361
  if (root) {
13919
14362
  applyBaseTokens(root);
13920
- applyBrandingTokens(root, branding, this.themeController.theme);
13921
- const tenantFontUrl = branding?.font_url ?? null;
13922
- applyDefaultFont(root, this.variant === "page" && !tenantFontUrl ? void 0 : null);
14363
+ applyBrandingTokens(root, branding);
14364
+ const page = this.variant === "page";
14365
+ const tenantFontUrl = page ? branding?.typography?.font_url ?? null : null;
14366
+ applyDefaultFont(root, page && !tenantFontUrl ? void 0 : null);
13923
14367
  applyFontUrl(root, tenantFontUrl);
13924
14368
  }
13925
14369
  this.dataset.theme = this.themeController.theme;
@@ -13955,7 +14399,7 @@ const TEMPLATE_NAMES = {
13955
14399
  };
13956
14400
  //#endregion
13957
14401
  //#region src/orchestrator/templates/layout-chrome.css?inline
13958
- var layout_chrome_default = ":host {\n color: var(--zl-foreground);\n font-family: var(--zl-font-family-sans);\n background: none;\n display: block;\n}\n\n:host([variant=\"page\"]) {\n background: var(--zl-background);\n min-height: 100%;\n}\n\nimg[data-zl-asset-broken] {\n display: none;\n}\n\n.zl-mount {\n min-height: var(--zl-page-min-height, auto);\n background: inherit;\n color: inherit;\n flex-direction: column;\n display: flex;\n container-type: inline-size;\n}\n\n:host([variant=\"page\"]) .zl-mount {\n min-height: var(--zl-page-min-height, 100vh);\n}\n\n.zl-mount > zl-page-shell, .zl-mount > [data-zl-template-root] {\n flex: auto;\n}\n\n.zl-attribution {\n background: none;\n justify-content: center;\n align-items: center;\n display: flex;\n}\n\n.zl-attribution[hidden] {\n display: none;\n}\n\n.zl-trustmark {\n gap: var(--zl-spacing-2-5);\n flex-wrap: wrap;\n}\n\n.zl-trustmark__mark {\n align-items: center;\n gap: var(--zl-spacing-2);\n font-family: var(--zl-font-family-sans);\n font-size: var(--zl-text-sm-size);\n line-height: var(--zl-text-sm-leading);\n color: var(--zl-foreground);\n text-decoration: none;\n display: inline-flex;\n}\n\n.zl-trustmark__mark:hover {\n text-underline-offset: 4px;\n text-decoration: underline;\n}\n\n.zl-trustmark__mark:focus-visible {\n outline: var(--zl-focus-width) solid var(--zl-ring);\n outline-offset: var(--zl-focus-offset);\n border-radius: var(--zl-radius-sm);\n}\n\n.zl-trustmark__logotype {\n flex-shrink: 0;\n width: 65px;\n height: 16px;\n line-height: 0;\n display: block;\n}\n\n.zl-trustmark__logotype-svg {\n width: 100%;\n height: 100%;\n display: block;\n}\n\n.zl-card-logo {\n width: auto;\n max-width: min(12rem, 60%);\n max-height: var(--zl-card-logo-max-height, 2.5rem);\n object-fit: contain;\n}\n\n.zl-card-title {\n font-family: var(--zl-font-family-heading);\n font-size: var(--zl-text-xl-size);\n font-weight: var(--zl-font-weight-medium);\n color: var(--zl-card-foreground);\n text-align: left;\n margin: 0;\n line-height: 1;\n}\n\n.zl-card-subtitle {\n font-family: var(--zl-font-family-sans);\n font-size: var(--zl-text-sm-size);\n line-height: var(--zl-text-sm-leading);\n font-weight: var(--zl-font-weight-normal);\n color: var(--zl-muted-foreground);\n margin: 0;\n}\n\n.zl-field-group {\n gap: var(--zl-spacing-7);\n flex-direction: column;\n display: flex;\n}\n\n.zl-field-group:not(:has(*)) {\n display: none;\n}\n\n.zl-card-actions {\n gap: var(--zl-spacing-3);\n flex-direction: column;\n display: flex;\n}\n\n[data-suppress-header] .zl-card-title, [data-suppress-header] .zl-card-subtitle {\n clip-path: inset(50%);\n white-space: nowrap;\n border: 0;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n position: absolute;\n overflow: hidden;\n}\n\n.zl-card-nav {\n font-family: var(--zl-font-family-sans);\n font-size: var(--zl-text-sm-size);\n line-height: var(--zl-text-sm-leading);\n font-weight: var(--zl-font-weight-normal);\n color: var(--zl-muted-foreground);\n text-align: center;\n margin: 0;\n}\n\n.zl-card-nav__link {\n font: inherit;\n line-height: inherit;\n color: var(--zl-link);\n text-underline-offset: 4px;\n cursor: pointer;\n background: none;\n border: none;\n margin: 0;\n padding: 0;\n text-decoration: underline;\n display: inline;\n}\n\n.zl-card-nav__link:hover {\n color: var(--zl-foreground);\n}\n\n.zl-card-nav__link:focus-visible {\n outline: var(--zl-focus-width) solid var(--zl-ring);\n outline-offset: var(--zl-focus-offset);\n}\n\n.zl-card-forgot {\n text-align: right;\n margin: 0;\n}\n\n.zl-card-forgot__link {\n font-family: var(--zl-font-family-sans);\n font-size: var(--zl-text-sm-size);\n line-height: var(--zl-text-sm-leading);\n font-weight: var(--zl-font-weight-normal);\n color: var(--zl-link);\n text-underline-offset: 4px;\n cursor: pointer;\n background: none;\n border: none;\n margin: 0;\n padding: 0;\n text-decoration: none;\n display: inline;\n}\n\n.zl-card-forgot__link:hover {\n text-decoration: underline;\n}\n\n.zl-card-forgot__link:focus-visible {\n outline: var(--zl-focus-width) solid var(--zl-ring);\n outline-offset: var(--zl-focus-offset);\n}\n\n.zl-split {\n grid-template-columns: var(--zl-split-columns, minmax(0, 1fr) minmax(0, 1fr));\n align-items: var(--zl-split-align, center);\n gap: var(--zl-spacing-8);\n width: 100%;\n min-height: 100%;\n display: grid;\n}\n\n.zl-split__brand {\n justify-content: center;\n align-items: center;\n gap: var(--zl-spacing-6);\n padding: var(--zl-spacing-8) var(--zl-spacing-4);\n flex-direction: column;\n display: flex;\n}\n\n.zl-split__logo {\n max-width: min(16rem, 60%);\n max-height: var(--zl-split-logo-max-height, 16rem);\n}\n\n.zl-split__logo:has( + .zl-split__hero) {\n max-height: var(--zl-split-logo-max-height, 8rem);\n}\n\n.zl-split__logo:has( + .zl-split__hero) + .zl-split__hero {\n max-height: min(var(--zl-split-hero-max-height, 22.5rem),\n max(6rem,\n calc(100vh - 104px - var(--zl-spacing-8) - var(--zl-spacing-8) -\n 2.5rem - var(--zl-spacing-6) - var(--zl-spacing-6) -\n var(--zl-split-logo-max-height, 8rem))));\n}\n\n.zl-split__hero {\n width: 100%;\n max-height: var(--zl-split-hero-max-height, min(22.5rem, 60vh));\n border-radius: var(--zl-radius-lg);\n object-fit: cover;\n}\n\n.zl-split__placeholder {\n aspect-ratio: 4 / 5;\n border-radius: var(--zl-radius-lg);\n background: radial-gradient(120% 120% at 18% 12%,\n var(--zl-gradient-lavender-start) 0%,\n transparent 60%),\n radial-gradient(130% 130% at 85% 92%,\n var(--zl-gradient-rose-start) 0%,\n transparent 60%),\n var(--zl-card);\n width: min(18rem, 85%);\n}\n\n.zl-split__form {\n justify-content: center;\n align-items: center;\n gap: var(--zl-spacing-6);\n flex-direction: column;\n padding: 16px;\n display: flex;\n}\n\n.zl-split__compact {\n max-width: min(12rem, 60%);\n display: none;\n}\n\nimg.zl-split__compact {\n max-height: 2.5rem;\n}\n\nimg.zl-split__compact--hero {\n border-radius: var(--zl-radius-lg);\n object-fit: cover;\n width: 100%;\n max-width: 100%;\n max-height: 6rem;\n}\n\n.zl-split--right .zl-split__brand {\n order: 2;\n}\n\n.zl-split--right .zl-split__form {\n order: 1;\n}\n\nzl-page-shell:has(.zl-split) [slot=\"footer\"].zl-attribution {\n grid-template-columns: var(--zl-split-columns, minmax(0, 1fr) minmax(0, 1fr));\n gap: var(--zl-spacing-8);\n width: 100%;\n display: grid;\n}\n\nzl-page-shell:has(.zl-split) [slot=\"footer\"].zl-attribution > * {\n grid-column: 2;\n justify-self: center;\n}\n\nzl-page-shell:has(.zl-split--right) [slot=\"footer\"].zl-attribution > * {\n grid-column: 1;\n}\n\nzl-page-shell:has(.zl-split) [slot=\"footer\"].zl-attribution ::slotted(*) {\n grid-column: 2;\n justify-self: center;\n}\n\nzl-page-shell:has(.zl-split--right) [slot=\"footer\"].zl-attribution ::slotted(*) {\n grid-column: 1;\n}\n\n@container (width <= 48rem) {\n .zl-split {\n grid-template-columns: minmax(0, 1fr);\n }\n\n .zl-split__brand {\n display: var(--zl-split-brand-mobile, none);\n }\n\n .zl-split__compact {\n display: block;\n }\n\n zl-page-shell:has(.zl-split) [slot=\"footer\"].zl-attribution {\n grid-template-columns: minmax(0, 1fr);\n }\n\n zl-page-shell:has(.zl-split) .zl-attribution > *, zl-page-shell:has(.zl-split--right) [slot=\"footer\"].zl-attribution > * {\n grid-column: 1;\n }\n}\n\n.zl-hero {\n text-align: left;\n width: 100%;\n max-width: 34rem;\n color: var(--zl-foreground);\n font-family: var(--zl-font-family-sans);\n flex-direction: column;\n display: flex;\n}\n\n.zl-hero__nav {\n margin-bottom: var(--zl-spacing-8);\n justify-content: space-between;\n align-items: center;\n display: flex;\n}\n\n.zl-hero__brand {\n align-items: center;\n gap: var(--zl-spacing-2);\n letter-spacing: -.01em;\n font-size: 1.125rem;\n font-weight: 700;\n display: inline-flex;\n}\n\n.zl-hero__brand-logo {\n max-height: 1.75rem;\n}\n\n.zl-hero__brand-mark {\n border-radius: var(--zl-radius-md);\n background: linear-gradient(135deg,\n var(--zl-gradient-lavender-start),\n var(--zl-gradient-rose-start));\n width: 1.75rem;\n height: 1.75rem;\n display: inline-flex;\n}\n\n.zl-hero__compact-brand {\n overflow-wrap: anywhere;\n letter-spacing: -.01em;\n max-width: 100%;\n color: var(--zl-foreground);\n margin: 0;\n font-size: 1.125rem;\n font-weight: 700;\n}\n\n.zl-hero__nav-links {\n gap: var(--zl-spacing-6);\n color: var(--zl-muted-foreground);\n font-size: .9375rem;\n display: flex;\n}\n\n.zl-hero__nav-links a {\n color: inherit;\n text-decoration: none;\n}\n\n.zl-hero__nav-links a:hover {\n text-decoration: underline;\n}\n\n.zl-hero__eyebrow {\n margin: 0 0 var(--zl-spacing-4);\n letter-spacing: .08em;\n text-transform: uppercase;\n color: var(--zl-muted-foreground);\n font-size: .8125rem;\n font-weight: 600;\n}\n\n.zl-hero__headline {\n margin: 0 0 var(--zl-spacing-4);\n font-family: var(--zl-font-family-heading);\n letter-spacing: -.02em;\n font-size: 2.75rem;\n font-weight: 700;\n line-height: 1.1;\n}\n\n.zl-hero__sub {\n margin: 0 0 var(--zl-spacing-8);\n max-width: 30rem;\n color: var(--zl-muted-foreground);\n font-size: 1.0625rem;\n line-height: 1.6;\n}\n\n.zl-hero__bullets {\n gap: var(--zl-spacing-2);\n margin: 0 0 var(--zl-spacing-8);\n flex-direction: column;\n padding: 0;\n list-style: none;\n display: flex;\n}\n\n.zl-hero__bullets li {\n align-items: center;\n gap: var(--zl-spacing-2);\n font-size: 1rem;\n display: flex;\n}\n\n.zl-hero__bullets li:before {\n content: \"✓\";\n color: var(--zl-success);\n font-weight: 700;\n}\n\n.zl-hero__footnote {\n padding-top: var(--zl-spacing-6);\n border-top: 1px solid var(--zl-border);\n color: var(--zl-muted-foreground);\n margin: 0;\n font-size: .8125rem;\n}\n\n.zl-hero__footnote strong {\n color: var(--zl-muted-foreground);\n font-weight: 600;\n}\n\n.zl-minimal {\n gap: var(--zl-spacing-6);\n flex-direction: column;\n width: min(24rem, 100%);\n margin-inline: auto;\n display: flex;\n}\n\n:host([variant=\"page\"]) .zl-minimal {\n padding: 52px 16px;\n}\n";
14402
+ var layout_chrome_default = ":host {\n color: var(--zl-foreground);\n font-family: var(--zl-font-family-sans);\n background: none;\n display: block;\n}\n\n:host([variant=\"page\"]) {\n background: var(--zl-background);\n min-height: 100%;\n}\n\nimg[data-zl-asset-broken] {\n display: none;\n}\n\n.zl-mount {\n min-height: var(--zl-page-min-height, auto);\n background: inherit;\n color: inherit;\n flex-direction: column;\n display: flex;\n container-type: inline-size;\n}\n\n:host([variant=\"page\"]) .zl-mount {\n min-height: var(--zl-page-min-height, 100vh);\n}\n\n.zl-mount > zl-page-shell, .zl-mount > [data-zl-template-root] {\n flex: auto;\n}\n\n.zl-attribution {\n background: none;\n justify-content: center;\n align-items: center;\n display: flex;\n}\n\n.zl-attribution[hidden] {\n display: none;\n}\n\n.zl-trustmark {\n gap: var(--zl-spacing-2-5);\n flex-wrap: wrap;\n}\n\n.zl-trustmark__mark {\n align-items: center;\n gap: var(--zl-spacing-2);\n font-family: var(--zl-font-family-sans);\n font-size: var(--zl-text-sm-size);\n line-height: var(--zl-text-sm-leading);\n color: var(--zl-foreground);\n text-decoration: none;\n display: inline-flex;\n}\n\n.zl-trustmark__mark:hover {\n text-underline-offset: 4px;\n text-decoration: underline;\n}\n\n.zl-trustmark__mark:focus-visible {\n outline: var(--zl-focus-width) solid var(--zl-ring);\n outline-offset: var(--zl-focus-offset);\n border-radius: var(--zl-radius-sm);\n}\n\n.zl-trustmark__logotype {\n flex-shrink: 0;\n width: 65px;\n height: 16px;\n line-height: 0;\n display: block;\n}\n\n.zl-trustmark__logotype-svg {\n width: 100%;\n height: 100%;\n display: block;\n}\n\n.zl-card-logo {\n width: auto;\n max-width: min(12rem, 60%);\n max-height: calc(var(--zl-card-logo-max-height, 2.5rem) * var(--zl-logo-scale, 1));\n object-fit: contain;\n}\n\n.zl-card-title {\n font-family: var(--zl-font-family-heading);\n font-size: var(--zl-text-xl-size);\n font-weight: var(--zl-font-weight-medium);\n color: var(--zl-card-foreground);\n text-align: left;\n margin: 0;\n line-height: 1;\n}\n\n.zl-card-subtitle {\n font-family: var(--zl-font-family-sans);\n font-size: var(--zl-text-sm-size);\n line-height: var(--zl-text-sm-leading);\n font-weight: var(--zl-font-weight-normal);\n color: var(--zl-muted-foreground);\n margin: 0;\n}\n\n.zl-field-group {\n gap: var(--zl-spacing-7);\n flex-direction: column;\n display: flex;\n}\n\n.zl-field-group:not(:has(*)) {\n display: none;\n}\n\n.zl-card-actions {\n gap: var(--zl-spacing-3);\n flex-direction: column;\n display: flex;\n}\n\n[data-suppress-header] .zl-card-title, [data-suppress-header] .zl-card-subtitle {\n clip-path: inset(50%);\n white-space: nowrap;\n border: 0;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n position: absolute;\n overflow: hidden;\n}\n\n.zl-card-nav {\n font-family: var(--zl-font-family-sans);\n font-size: var(--zl-text-sm-size);\n line-height: var(--zl-text-sm-leading);\n font-weight: var(--zl-font-weight-normal);\n color: var(--zl-muted-foreground);\n text-align: center;\n margin: 0;\n}\n\n.zl-card-nav__link {\n font: inherit;\n line-height: inherit;\n color: var(--zl-link);\n text-underline-offset: 4px;\n cursor: pointer;\n background: none;\n border: none;\n margin: 0;\n padding: 0;\n text-decoration: underline;\n display: inline;\n}\n\n.zl-card-nav__link:hover {\n color: var(--zl-foreground);\n}\n\n.zl-card-nav__link:focus-visible {\n outline: var(--zl-focus-width) solid var(--zl-ring);\n outline-offset: var(--zl-focus-offset);\n}\n\n.zl-card-forgot {\n text-align: right;\n margin: 0;\n}\n\n.zl-card-forgot__link {\n font-family: var(--zl-font-family-sans);\n font-size: var(--zl-text-sm-size);\n line-height: var(--zl-text-sm-leading);\n font-weight: var(--zl-font-weight-normal);\n color: var(--zl-link);\n text-underline-offset: 4px;\n cursor: pointer;\n background: none;\n border: none;\n margin: 0;\n padding: 0;\n text-decoration: none;\n display: inline;\n}\n\n.zl-card-forgot__link:hover {\n text-decoration: underline;\n}\n\n.zl-card-forgot__link:focus-visible {\n outline: var(--zl-focus-width) solid var(--zl-ring);\n outline-offset: var(--zl-focus-offset);\n}\n\n.zl-split {\n grid-template-columns: var(--zl-split-columns, minmax(0, 1fr) minmax(0, 1fr));\n align-items: var(--zl-split-align, center);\n gap: var(--zl-spacing-8);\n width: 100%;\n min-height: 100%;\n display: grid;\n}\n\n.zl-split__brand {\n justify-content: center;\n align-items: center;\n gap: var(--zl-spacing-6);\n padding: var(--zl-spacing-8) var(--zl-spacing-4);\n flex-direction: column;\n display: flex;\n}\n\n.zl-split__logo {\n max-width: min(16rem, 60%);\n max-height: calc(var(--zl-split-logo-max-height, 16rem) * var(--zl-logo-scale, 1));\n}\n\n.zl-split__logo:has( + .zl-split__hero) {\n max-height: calc(var(--zl-split-logo-max-height, 8rem) * var(--zl-logo-scale, 1));\n}\n\n.zl-split__logo:has( + .zl-split__hero) + .zl-split__hero {\n max-height: min(var(--zl-split-hero-max-height, 22.5rem),\n max(6rem,\n calc(100vh - 104px - var(--zl-spacing-8) - var(--zl-spacing-8) -\n 2.5rem - var(--zl-spacing-6) - var(--zl-spacing-6) -\n calc(var(--zl-split-logo-max-height, 8rem) * var(--zl-logo-scale, 1)))));\n}\n\n.zl-split__hero {\n width: 100%;\n max-height: var(--zl-split-hero-max-height, min(22.5rem, 60vh));\n border-radius: var(--zl-radius-lg);\n object-fit: cover;\n}\n\n.zl-split__placeholder {\n aspect-ratio: 4 / 5;\n border-radius: var(--zl-radius-lg);\n background: radial-gradient(120% 120% at 18% 12%,\n var(--zl-gradient-lavender-start) 0%,\n transparent 60%),\n radial-gradient(130% 130% at 85% 92%,\n var(--zl-gradient-rose-start) 0%,\n transparent 60%),\n var(--zl-card);\n width: min(18rem, 85%);\n}\n\n.zl-split__form {\n justify-content: center;\n align-items: center;\n gap: var(--zl-spacing-6);\n flex-direction: column;\n padding: 16px;\n display: flex;\n}\n\n.zl-split__compact {\n max-width: min(12rem, 60%);\n display: none;\n}\n\nimg.zl-split__compact {\n max-height: calc(2.5rem * var(--zl-logo-scale, 1));\n}\n\nimg.zl-split__compact--hero {\n border-radius: var(--zl-radius-lg);\n object-fit: cover;\n width: 100%;\n max-width: 100%;\n max-height: 6rem;\n}\n\n.zl-split--right .zl-split__brand {\n order: 2;\n}\n\n.zl-split--right .zl-split__form {\n order: 1;\n}\n\nzl-page-shell:has(.zl-split) [slot=\"footer\"].zl-attribution {\n grid-template-columns: var(--zl-split-columns, minmax(0, 1fr) minmax(0, 1fr));\n gap: var(--zl-spacing-8);\n width: 100%;\n display: grid;\n}\n\nzl-page-shell:has(.zl-split) [slot=\"footer\"].zl-attribution > * {\n grid-column: 2;\n justify-self: center;\n}\n\nzl-page-shell:has(.zl-split--right) [slot=\"footer\"].zl-attribution > * {\n grid-column: 1;\n}\n\nzl-page-shell:has(.zl-split) [slot=\"footer\"].zl-attribution ::slotted(*) {\n grid-column: 2;\n justify-self: center;\n}\n\nzl-page-shell:has(.zl-split--right) [slot=\"footer\"].zl-attribution ::slotted(*) {\n grid-column: 1;\n}\n\n@container (width <= 48rem) {\n .zl-split {\n grid-template-columns: minmax(0, 1fr);\n }\n\n .zl-split__brand {\n display: var(--zl-split-brand-mobile, none);\n }\n\n .zl-split__compact {\n display: block;\n }\n\n zl-page-shell:has(.zl-split) [slot=\"footer\"].zl-attribution {\n grid-template-columns: minmax(0, 1fr);\n }\n\n zl-page-shell:has(.zl-split) .zl-attribution > *, zl-page-shell:has(.zl-split--right) [slot=\"footer\"].zl-attribution > * {\n grid-column: 1;\n }\n}\n\n.zl-hero {\n text-align: left;\n width: 100%;\n max-width: 34rem;\n color: var(--zl-foreground);\n font-family: var(--zl-font-family-sans);\n flex-direction: column;\n display: flex;\n}\n\n.zl-hero__nav {\n margin-bottom: var(--zl-spacing-8);\n justify-content: space-between;\n align-items: center;\n display: flex;\n}\n\n.zl-hero__brand {\n align-items: center;\n gap: var(--zl-spacing-2);\n letter-spacing: -.01em;\n font-size: 1.125rem;\n font-weight: 700;\n display: inline-flex;\n}\n\n.zl-hero__brand-logo {\n max-height: calc(1.75rem * var(--zl-logo-scale, 1));\n}\n\n.zl-hero__brand-mark {\n border-radius: var(--zl-radius-md);\n background: linear-gradient(135deg,\n var(--zl-gradient-lavender-start),\n var(--zl-gradient-rose-start));\n width: 1.75rem;\n height: 1.75rem;\n display: inline-flex;\n}\n\n.zl-hero__compact-brand {\n overflow-wrap: anywhere;\n letter-spacing: -.01em;\n max-width: 100%;\n color: var(--zl-foreground);\n margin: 0;\n font-size: 1.125rem;\n font-weight: 700;\n}\n\n.zl-hero__nav-links {\n gap: var(--zl-spacing-6);\n color: var(--zl-muted-foreground);\n font-size: .9375rem;\n display: flex;\n}\n\n.zl-hero__nav-links a {\n color: inherit;\n text-decoration: none;\n}\n\n.zl-hero__nav-links a:hover {\n text-decoration: underline;\n}\n\n.zl-hero__eyebrow {\n margin: 0 0 var(--zl-spacing-4);\n letter-spacing: .08em;\n text-transform: uppercase;\n color: var(--zl-muted-foreground);\n font-size: .8125rem;\n font-weight: 600;\n}\n\n.zl-hero__headline {\n margin: 0 0 var(--zl-spacing-4);\n font-family: var(--zl-font-family-heading);\n letter-spacing: -.02em;\n font-size: 2.75rem;\n font-weight: 700;\n line-height: 1.1;\n}\n\n.zl-hero__sub {\n margin: 0 0 var(--zl-spacing-8);\n max-width: 30rem;\n color: var(--zl-muted-foreground);\n font-size: 1.0625rem;\n line-height: 1.6;\n}\n\n.zl-hero__bullets {\n gap: var(--zl-spacing-2);\n margin: 0 0 var(--zl-spacing-8);\n flex-direction: column;\n padding: 0;\n list-style: none;\n display: flex;\n}\n\n.zl-hero__bullets li {\n align-items: center;\n gap: var(--zl-spacing-2);\n font-size: 1rem;\n display: flex;\n}\n\n.zl-hero__bullets li:before {\n content: \"✓\";\n color: var(--zl-success);\n font-weight: 700;\n}\n\n.zl-hero__footnote {\n padding-top: var(--zl-spacing-6);\n border-top: 1px solid var(--zl-border);\n color: var(--zl-muted-foreground);\n margin: 0;\n font-size: .8125rem;\n}\n\n.zl-hero__footnote strong {\n color: var(--zl-muted-foreground);\n font-weight: 600;\n}\n\n.zl-minimal {\n gap: var(--zl-spacing-6);\n flex-direction: column;\n width: min(24rem, 100%);\n margin-inline: auto;\n display: flex;\n}\n\n:host([variant=\"page\"]) .zl-minimal {\n padding: 52px 16px;\n}\n";
13959
14403
  //#endregion
13960
14404
  //#region src/orchestrator/zitadel-login.ts
13961
14405
  /** Narrow a rendered named element to the `formValue` field-atom contract. */
@@ -14086,6 +14530,13 @@ let ZitadelLogin = class ZitadelLogin extends ZitadelSurface {
14086
14530
  this.#_formValues_accessor_storage = value;
14087
14531
  }
14088
14532
  engine = null;
14533
+ /**
14534
+ * The theme the last commit rendered with. The template output carries the
14535
+ * resolved side's mark, so a theme change rewrites the string and
14536
+ * `unsafeHTML` rebuilds the form — which drops what the visitor had typed
14537
+ * unless it is put back.
14538
+ */
14539
+ lastRenderedTheme = null;
14089
14540
  sanitise = createSanitiser();
14090
14541
  /**
14091
14542
  * Cached compiled tenant template, keyed by source string. Re-rendering on
@@ -14184,9 +14635,14 @@ let ZitadelLogin = class ZitadelLogin extends ZitadelSurface {
14184
14635
  }
14185
14636
  for (const card of this.shadowRoot.querySelectorAll("zl-card")) card.toggleAttribute("data-suppress-header", this.suppressHeader);
14186
14637
  }
14638
+ const previousTheme = this.lastRenderedTheme;
14639
+ this.lastRenderedTheme = this.themeController.theme;
14187
14640
  const props = changed;
14188
- if (!props.has("response")) return;
14189
- this.hydrateStepAfterRender(props.get("response") == null);
14641
+ if (props.has("response")) {
14642
+ this.hydrateStepAfterRender(props.get("response") == null);
14643
+ return;
14644
+ }
14645
+ if (previousTheme !== null && previousTheme !== this.lastRenderedTheme) this.restoreValuesAfterRender();
14190
14646
  }
14191
14647
  /**
14192
14648
  * Apply captured values and move focus once the new step has fully
@@ -14201,11 +14657,19 @@ let ZitadelLogin = class ZitadelLogin extends ZitadelSurface {
14201
14657
  * swaps are user-initiated, so focus moves in both modes.
14202
14658
  */
14203
14659
  async hydrateStepAfterRender(initial = false) {
14660
+ await this.restoreValuesAfterRender();
14661
+ if (!initial || this.variant === "page") this.moveFocusToFirstField(initial && this.variant === "page");
14662
+ }
14663
+ /**
14664
+ * Put captured values back once the rebuilt subtree has rendered. Awaits the
14665
+ * child atoms' own first render before touching them, rather than guessing a
14666
+ * frame with `requestAnimationFrame`.
14667
+ */
14668
+ async restoreValuesAfterRender() {
14204
14669
  await this.updateComplete;
14205
14670
  const atoms = this.shadowRoot?.querySelectorAll("zl-field, zl-select, zl-checkbox, zl-button");
14206
14671
  if (atoms) await Promise.all(Array.from(atoms).map((atom) => atom.updateComplete));
14207
14672
  this.applyValuesToFields();
14208
- if (!initial || this.variant === "page") this.moveFocusToFirstField(initial && this.variant === "page");
14209
14673
  }
14210
14674
  render() {
14211
14675
  if (this.startupError) return b`<form class="zl-mount" novalidate>
@@ -14247,6 +14711,20 @@ let ZitadelLogin = class ZitadelLogin extends ZitadelSurface {
14247
14711
  return rendered + html;
14248
14712
  }
14249
14713
  /**
14714
+ * The branding a template renders against, with `logo_url` already resolved
14715
+ * to the mark for the active side. Templates read one logo field and get the
14716
+ * right file per surface; the per-side URLs stay on `theme` for a template
14717
+ * that wants to reach them itself.
14718
+ */
14719
+ brandingForTemplate() {
14720
+ if (!this.branding) return {};
14721
+ const logoUrl = resolveLogoUrl(this.branding, this.themeController.theme);
14722
+ return {
14723
+ ...this.branding,
14724
+ logo_url: logoUrl
14725
+ };
14726
+ }
14727
+ /**
14250
14728
  * "Secured with Zitadel" attribution chrome injected into every
14251
14729
  * template's page-shell footer slot. Controlled by
14252
14730
  * `branding.attribution.show_zitadel` — defaults to `true` for
@@ -14327,7 +14805,11 @@ let ZitadelLogin = class ZitadelLogin extends ZitadelSurface {
14327
14805
  this.armed = true;
14328
14806
  } else if (!hasBack && this.armed) {
14329
14807
  this.armed = false;
14330
- if (history.state?.zl === true) {
14808
+ if (history.state?.zl === true) if (this.completing) history.replaceState({
14809
+ ...history.state,
14810
+ zl: false
14811
+ }, "");
14812
+ else {
14331
14813
  this.ignoreNextPop = true;
14332
14814
  history.back();
14333
14815
  }
@@ -14399,7 +14881,7 @@ let ZitadelLogin = class ZitadelLogin extends ZitadelSurface {
14399
14881
  messages: [],
14400
14882
  identity: this.deriveIdentity(),
14401
14883
  errors,
14402
- branding: this.branding ?? {},
14884
+ branding: this.brandingForTemplate(),
14403
14885
  loading: this.loading
14404
14886
  };
14405
14887
  let raw;
@@ -15063,7 +15545,7 @@ let ZitadelLogout = class ZitadelLogout extends i$2 {
15063
15545
  const root = this.shadowRoot;
15064
15546
  if (root && !this.templateMode) {
15065
15547
  applyBaseTokens(root);
15066
- applyBrandingTokens(root, void 0, this.themeController.theme);
15548
+ applyBrandingTokens(root, void 0);
15067
15549
  }
15068
15550
  this.dataset.theme = this.themeController.theme;
15069
15551
  this.toggleAttribute("data-theme-dark", this.themeController.theme === "dark");
@@ -15547,4 +16029,4 @@ __decorate([r$1()], ZitadelSession.prototype, "loading", null);
15547
16029
  __decorate([r$1()], ZitadelSession.prototype, "errorMessage", null);
15548
16030
  ZitadelSession = __decorate([t$4("zitadel-session")], ZitadelSession);
15549
16031
  //#endregion
15550
- export { TEMPLATE_NAMES, ThemeController, ZITADEL_ATTRIBUTION_LOGOTYPE_SVG, ZitadelLogin, ZitadelLogout, ZitadelSession, ZlAlert, ZlButton, ZlCard, ZlCheckbox, ZlField, ZlIcon, ZlPageShell, ZlPill, ZlSelect, applyBrandingTokens, applyFontUrl, baseHostStyles, buildBrandingStylesheet, builtinLocales, businessLocales, createSanitiser, cssVars, de, default_default as defaultTemplate, en, findManifest, focusVisibleStyles, getCurrentStep, it, layout_chrome_default as layoutChromeCss, listKnownTags, manifestRegistry, patchMandatoryGates, resolveTheme, startFlow, submitStep, t, tokens, validateBranding, zitadelTrustmarkInnerHtml, zlAlertManifest, zlButtonManifest, zlCardManifest, zlCheckboxManifest, zlFieldManifest, zlIconManifest, zlPageShellManifest, zlPillManifest, zlSelectManifest };
16032
+ export { TEMPLATE_NAMES, ThemeController, ZITADEL_ATTRIBUTION_LOGOTYPE_SVG, ZitadelLogin, ZitadelLogout, ZitadelSession, ZlAlert, ZlButton, ZlCard, ZlCheckbox, ZlField, ZlIcon, ZlPageShell, ZlPill, ZlSelect, applyBrandingTokens, applyFontUrl, baseHostStyles, buildBrandingStylesheet, builtinLocales, businessLocales, createSanitiser, cssVars, de, default_default as defaultTemplate, en, findManifest, focusVisibleStyles, getCurrentStep, it, layout_chrome_default as layoutChromeCss, listKnownTags, manifestRegistry, patchMandatoryGates, publishedSides, resolveLogoUrl, resolveTheme, startFlow, submitStep, t, tokens, validateBranding, zitadelTrustmarkInnerHtml, zlAlertManifest, zlButtonManifest, zlCardManifest, zlCheckboxManifest, zlFieldManifest, zlIconManifest, zlPageShellManifest, zlPillManifest, zlSelectManifest };