asdsad
All checks were successful
Deploy / deploy-staging (push) Successful in 52s
Deploy / deploy-production (push) Has been skipped

This commit is contained in:
2026-06-10 22:44:28 +02:00
parent a8f4a4b36a
commit cd5525ff2e
20 changed files with 1951 additions and 351 deletions

View File

@@ -1,146 +1,121 @@
<#import "template.ftl" as layout>
<#import "passkeys.ftl" as passkeys>
<@layout.registrationLayout displayMessage=!messagesPerField.existsError('username','password') displayInfo=realm.password && realm.registrationAllowed && !registrationDisabled??; section>
<@layout.registrationLayout displayMessage=!messagesPerField.existsError('username','password') displayInfo=false; section>
<#if section = "header">
${msg("loginAccountTitle")}
Desktop-Anmeldung
<#elseif section = "form">
<section class="kb-login-shell">
<div class="kb-login-curtains" aria-hidden="true">
<span class="kb-login-curtain kb-login-curtain-top kb-login-curtain-top-a"></span>
<span class="kb-login-curtain kb-login-curtain-top kb-login-curtain-top-b"></span>
<span class="kb-login-curtain kb-login-curtain-bottom kb-login-curtain-bottom-a"></span>
<span class="kb-login-curtain kb-login-curtain-bottom kb-login-curtain-bottom-b"></span>
</div>
<div class="kb-login-ambient" aria-hidden="true"></div>
<div class="kb-login-backdrop" aria-hidden="true"></div>
<div class="kb-login-panel">
<div class="kb-login-copy">
<p class="kb-login-kicker">Kusche.Berlin</p>
<h1 class="kb-login-title">${msg("kbDesktopLoginHeadline")}</h1>
<p class="kb-login-subtitle">${msg("kbDesktopLoginSubheadline")}</p>
</div>
<div class="kb-login-stage">
<header class="kb-login-clock" aria-live="polite">
<p class="kb-login-date" id="kb-login-date">${msg("kbClockDateFallback", "Montag, 18. November")}</p>
<p class="kb-login-time" id="kb-login-time">${msg("kbClockTimeFallback", "4:20")}</p>
</header>
<div id="kc-form" class="kb-login-form-shell">
<div id="kc-form-wrapper">
<#if realm.password>
<form id="kc-form-login" onsubmit="login.disabled = true; return true;" action="${url.loginAction}" method="post">
<#if !usernameHidden??>
<div class="${properties.kcFormGroupClass!}">
<label for="username" class="${properties.kcLabelClass!}">
<#if !realm.loginWithEmailAllowed>
${msg("username")}
<#elseif !realm.registrationEmailAsUsername>
${msg("usernameOrEmail")}
<#else>
${msg("email")}
<div class="kb-login-card">
<div class="kb-login-brand">
<p class="kb-login-kicker">Kusche.Berlin</p>
</div>
<div id="kc-form" class="kb-login-form-shell">
<div id="kc-form-wrapper">
<#if realm.password>
<form id="kc-form-login" onsubmit="login.disabled = true; return true;" action="${url.loginAction}" method="post">
<#if !usernameHidden??>
<div class="kb-form-group">
<input tabindex="2" id="username" class="kb-text-input" name="username" value="${(login.username!'')}" type="text"
autofocus autocomplete="${(enableWebAuthnConditionalUI?has_content)?then('username webauthn', 'username')}"
aria-invalid="<#if messagesPerField.existsError('username','password')>true</#if>"
dir="ltr"
placeholder="Benutzername oder E-Mail eingeben" />
<#if messagesPerField.existsError('username','password')>
<span id="input-error" class="kb-input-error" aria-live="polite">
${kcSanitize(messagesPerField.getFirstError('username','password'))?no_esc}
</span>
</#if>
</label>
</div>
</#if>
<input tabindex="2" id="username" class="${properties.kcInputClass!}" name="username" value="${(login.username!'')}" type="text"
autofocus autocomplete="${(enableWebAuthnConditionalUI?has_content)?then('username webauthn', 'username')}"
aria-invalid="<#if messagesPerField.existsError('username','password')>true</#if>"
dir="ltr" />
<div class="kb-form-group">
<div class="kb-password-row" dir="ltr">
<input tabindex="3" id="password" class="kb-text-input kb-text-input-password" name="password" type="password" autocomplete="current-password"
aria-invalid="<#if messagesPerField.existsError('username','password')>true</#if>"
placeholder="Passwort eingeben" />
<#if messagesPerField.existsError('username','password')>
<span id="input-error" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
<button class="kb-password-toggle" type="button" aria-label="${msg("showPassword")}"
aria-controls="password" data-password-toggle tabindex="4"
data-icon-show="${properties.kcFormPasswordVisibilityIconShow!}" data-icon-hide="${properties.kcFormPasswordVisibilityIconHide!}"
data-label-show="${msg('showPassword')}" data-label-hide="${msg('hidePassword')}">
<span aria-hidden="true">◉</span>
</button>
</div>
<#if usernameHidden?? && messagesPerField.existsError('username','password')>
<span id="input-error" class="kb-input-error" aria-live="polite">
${kcSanitize(messagesPerField.getFirstError('username','password'))?no_esc}
</span>
</#if>
</div>
</#if>
<div class="${properties.kcFormGroupClass!}">
<label for="password" class="${properties.kcLabelClass!}">${msg("password")}</label>
<div class="${properties.kcInputGroup!}" dir="ltr">
<input tabindex="3" id="password" class="${properties.kcInputClass!}" name="password" type="password" autocomplete="current-password"
aria-invalid="<#if messagesPerField.existsError('username','password')>true</#if>" />
<button class="${properties.kcFormPasswordVisibilityButtonClass!}" type="button" aria-label="${msg("showPassword")}"
aria-controls="password" data-password-toggle tabindex="4"
data-icon-show="${properties.kcFormPasswordVisibilityIconShow!}" data-icon-hide="${properties.kcFormPasswordVisibilityIconHide!}"
data-label-show="${msg('showPassword')}" data-label-hide="${msg('hidePassword')}">
<i class="${properties.kcFormPasswordVisibilityIconShow!}" aria-hidden="true"></i>
</button>
</div>
<#if usernameHidden?? && messagesPerField.existsError('username','password')>
<span id="input-error" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
${kcSanitize(messagesPerField.getFirstError('username','password'))?no_esc}
</span>
</#if>
</div>
<div class="${properties.kcFormGroupClass!} ${properties.kcFormSettingClass!}">
<div id="kc-form-options">
<#if realm.rememberMe && !usernameHidden??>
<div class="checkbox">
<label>
<div class="kb-form-meta">
<div class="kb-form-options" id="kc-form-options">
<#if !usernameHidden??>
<label class="kb-remember">
<#if login.rememberMe??>
<input tabindex="5" id="rememberMe" name="rememberMe" type="checkbox" checked> ${msg("rememberMe")}
<input tabindex="5" id="rememberMe" name="rememberMe" type="checkbox" checked>
<#else>
<input tabindex="5" id="rememberMe" name="rememberMe" type="checkbox"> ${msg("rememberMe")}
<input tabindex="5" id="rememberMe" name="rememberMe" type="checkbox">
</#if>
<span>Angemeldet bleiben</span>
</label>
</div>
</#if>
</#if>
</div>
<div class="kb-form-links">
<#if (url.loginResetCredentialsUrl!'')?has_content>
<a tabindex="6" href="${url.loginResetCredentialsUrl}">Passwort vergessen?</a>
</#if>
</div>
</div>
<div class="${properties.kcFormOptionsWrapperClass!}">
<#if realm.resetPasswordAllowed>
<span><a tabindex="6" href="${url.loginResetCredentialsUrl}">${msg("doForgotPassword")}</a></span>
</#if>
<div id="kc-form-buttons" class="kb-form-submit">
<input type="hidden" id="id-hidden-input" name="credentialId" <#if auth.selectedCredential?has_content>value="${auth.selectedCredential}"</#if>/>
<input tabindex="7" class="kb-submit-button" name="login" id="kc-login" type="submit" value="Anmelden"/>
</div>
</div>
<div id="kc-form-buttons" class="${properties.kcFormGroupClass!}">
<input type="hidden" id="id-hidden-input" name="credentialId" <#if auth.selectedCredential?has_content>value="${auth.selectedCredential}"</#if>/>
<input tabindex="7" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}" name="login" id="kc-login" type="submit" value="${msg("doLogIn")}"/>
</div>
</form>
</#if>
<div id="kc-registration-container" class="kb-registration">
<div id="kc-registration">
<a class="kb-register-button" id="kb-register-button" tabindex="8" href="#" hidden>Registrieren</a>
</div>
</div>
</form>
</#if>
</div>
</div>
<aside class="kb-login-info">
<strong>Willkommen im Familienbereich</strong>
<p>Dieser geschützte Dashboardbereich ist für die Familie Kusche und enge Freunde gedacht. Hier entstehen persönliche Übersichten, gemeinsame Tools, interne Informationen und weitere digitale Bereiche für Alltag, Organisation und Zusammenarbeit.</p>
</aside>
</div>
</div>
</section>
<@passkeys.conditionalUIData />
<script type="module" src="${url.resourcesPath}/js/passwordVisibility.js"></script>
<script src="${url.resourcesPath}/js/login-clock.js"></script>
<script src="${url.resourcesPath}/js/desktop-register-link.js"></script>
<#elseif section = "info" >
<#if realm.password && realm.registrationAllowed && !registrationDisabled??>
<div id="kc-registration-container">
<div id="kc-registration">
<span>${msg("noAccount")} <a tabindex="8" href="${url.registrationUrl}">${msg("doRegister")}</a></span>
</div>
</div>
</#if>
<#-- Intentionally unused. Registration is rendered inside the form section. -->
<#elseif section = "socialProviders" >
<#if realm.password && social?? && social.providers?has_content>
<div id="kc-social-providers" class="${properties.kcFormSocialAccountSectionClass!}">
<hr/>
<h2>${msg("identity-provider-login-label")}</h2>
<ul class="${properties.kcFormSocialAccountListClass!} <#if social.providers?size gt 3>${properties.kcFormSocialAccountListGridClass!}</#if>">
<#list social.providers as p>
<li>
<a data-once-link data-disabled-class="${properties.kcFormSocialAccountListButtonDisabledClass!}" id="social-${p.alias}"
class="${properties.kcFormSocialAccountListButtonClass!} <#if social.providers?size gt 3>${properties.kcFormSocialAccountGridItem!}</#if>"
type="button" href="${p.loginUrl}">
<#if p.iconClasses?has_content>
<i class="${properties.kcCommonLogoIdP!} ${p.iconClasses!}" aria-hidden="true"></i>
<span class="${properties.kcFormSocialAccountNameClass!} kc-social-icon-text">${p.displayName!}</span>
<#else>
<span class="${properties.kcFormSocialAccountNameClass!}">${p.displayName!}</span>
</#if>
</a>
</li>
</#list>
</ul>
</div>
</#if>
<#-- Social logins are intentionally hidden for this theme. -->
</#if>
</@layout.registrationLayout>

View File

@@ -0,0 +1,16 @@
kbDesktopLoginHeadline=Desktop Login
kbDesktopLoginSubheadline=Melde dich mit deinem Kusche.Berlin Konto an, um auf Desktop, Apps und geschützte Inhalte zuzugreifen.
kbUsernameLabel=Benutzername
kbUsernameOrEmailLabel=Benutzername oder E-Mail
kbEmailLabel=E-Mail
kbPasswordLabel=Passwort
kbUsernamePlaceholder=Benutzername oder E-Mail eingeben
kbPasswordPlaceholder=Passwort eingeben
kbRememberMeLabel=Angemeldet bleiben
kbForgotPasswordLabel=Passwort vergessen?
kbLoginButtonLabel=Anmelden
kbRegisterButtonLabel=Registrieren
kbClockDateFallback=Montag, 18. November
kbClockTimeFallback=4:20
kbInfoTitle=Willkommen im Familienbereich
kbInfoBody=Dieser geschützte Dashboardbereich ist für die Familie Kusche und enge Freunde gedacht. Hier entstehen persönliche Übersichten, gemeinsame Tools, interne Informationen und künftig weitere digitale Bereiche für Alltag, Organisation und Zusammenarbeit.

View File

@@ -1,2 +1,16 @@
kbDesktopLoginHeadline=Desktop Login
kbDesktopLoginSubheadline=Geschuetzter Zugang zur Desktop-Shell und kuenftigen Modulen.
kbDesktopLoginSubheadline=Melde dich mit deinem Kusche.Berlin Konto an, um auf Desktop, Apps und geschützte Inhalte zuzugreifen.
kbUsernameLabel=Benutzername
kbUsernameOrEmailLabel=Benutzername oder E-Mail
kbEmailLabel=E-Mail
kbPasswordLabel=Passwort
kbUsernamePlaceholder=Benutzername oder E-Mail eingeben
kbPasswordPlaceholder=Passwort eingeben
kbRememberMeLabel=Angemeldet bleiben
kbForgotPasswordLabel=Passwort vergessen?
kbLoginButtonLabel=Anmelden
kbRegisterButtonLabel=Registrieren
kbClockDateFallback=Montag, 18. November
kbClockTimeFallback=4:20
kbInfoTitle=Willkommen im Familienbereich
kbInfoBody=Dieser geschützte Dashboardbereich ist für die Familie Kusche und enge Freunde gedacht. Hier entstehen persönliche Übersichten, gemeinsame Tools, interne Informationen und künftig weitere digitale Bereiche für Alltag, Organisation und Zusammenarbeit.

View File

@@ -1,2 +1,16 @@
kbDesktopLoginHeadline=Desktop Login
kbDesktopLoginSubheadline=Protected access to the desktop shell and upcoming modules.
kbDesktopLoginSubheadline=Sign in with your Kusche.Berlin account to access the desktop, apps and protected content.
kbUsernameLabel=Username
kbUsernameOrEmailLabel=Username or email
kbEmailLabel=Email
kbPasswordLabel=Password
kbUsernamePlaceholder=Enter your username or email
kbPasswordPlaceholder=Enter your password
kbRememberMeLabel=Keep me signed in
kbForgotPasswordLabel=Forgot password?
kbLoginButtonLabel=Sign in
kbRegisterButtonLabel=Register
kbClockDateFallback=Monday, November 18
kbClockTimeFallback=4:20
kbInfoTitle=Welcome to the family area
kbInfoBody=This protected dashboard area is intended for the Kusche family and close friends. It will host personal overviews, shared tools, internal information and additional digital spaces for daily life, organization and collaboration.

View File

@@ -1,307 +1,339 @@
html,
body {
min-height: 100%;
width: 100%;
}
html {
margin: 0;
padding: 0;
background: transparent !important;
}
body {
margin: 0;
overflow: hidden;
background:
radial-gradient(circle at top, rgba(255, 221, 177, 0.30), transparent 26%),
radial-gradient(circle at 80% 18%, rgba(132, 216, 166, 0.18), transparent 20%),
linear-gradient(145deg, #101827 0%, #18253d 52%, #0f172a 100%) !important;
radial-gradient(circle at 18% 84%, rgba(142, 214, 119, 0.22), transparent 18%),
linear-gradient(0deg, rgba(14, 27, 19, 0.28), rgba(14, 27, 19, 0.28)),
linear-gradient(135deg, #26442b 0%, #355939 26%, #5f4a2f 58%, #22351f 100%) !important;
color: #f8fafc;
font-family: "Roboto", "Helvetica Neue", Arial, sans-serif;
}
body > div,
body > main {
margin: 0;
}
#kc-header-wrapper,
#kc-header,
.login-pf-header,
#kc-page-title {
#kc-page-title,
#kc-locale,
#kc-social-providers {
display: none !important;
}
#kc-content,
#kc-content-wrapper,
#kc-locale {
#kc-content,
#kc-form,
#kc-form-wrapper,
.login-pf-page,
.card-pf,
.pf-v5-c-login,
.pf-v5-c-login__container,
.pf-v5-c-login__main,
.pf-v5-c-login__main-body,
.pf-v5-c-login__main-header,
.pf-v5-c-login__main-footer {
margin: 0 !important;
padding: 0 !important;
max-width: none !important;
width: 100% !important;
border: 0 !important;
background: transparent !important;
box-shadow: none !important;
}
#kc-content-wrapper,
#kc-content,
.login-pf-page,
.pf-v5-c-login,
.pf-v5-c-login__container,
.pf-v5-c-login__main,
.pf-v5-c-login__main-body {
min-height: 100vh !important;
}
#kc-content,
#kc-content-wrapper {
position: relative;
z-index: 2;
}
#kc-content-wrapper {
padding-top: 48px;
}
#kc-form-login .pf-v5-c-form__label-text,
#kc-form-login label,
#kc-form-options,
#kc-form-options a,
#kc-registration,
#kc-registration a,
#kc-social-providers h2,
#kc-social-providers a {
color: #e2e8f0 !important;
.pf-v5-c-alert,
.alert,
#kc-info-message,
#kc-error-message {
margin: 0 0 14px !important;
border: 0 !important;
border-radius: 12px !important;
background: rgba(127, 29, 29, 0.24) !important;
color: #fee2e2 !important;
box-shadow: none !important;
}
.kb-login-shell {
position: relative;
isolation: isolate;
display: grid;
place-items: center;
min-height: calc(100vh - 80px);
padding: 24px;
min-height: 100vh;
padding: 24px 20px 30px;
}
.kb-login-curtains {
position: absolute;
inset: 0;
z-index: 0;
pointer-events: none;
}
.kb-login-curtain {
position: absolute;
left: 50%;
width: 180vmax;
height: 180vmax;
opacity: 0.72;
border-radius: 999px;
transform-origin: 0 50%;
animation-duration: 1.1s;
animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
animation-fill-mode: both;
}
.kb-login-curtain-top,
.kb-login-curtain-bottom {
top: 50%;
margin-top: -90vmax;
}
.kb-login-curtain-top-a {
background: linear-gradient(135deg, rgba(236, 101, 105, 0.84), rgba(238, 175, 129, 0.76));
animation-name: kbLoginCurtainTopA;
}
.kb-login-curtain-top-b {
background: linear-gradient(135deg, rgba(255, 196, 104, 0.66), rgba(255, 138, 76, 0.6));
animation-name: kbLoginCurtainTopB;
animation-delay: 0.08s;
}
.kb-login-curtain-bottom-a {
background: linear-gradient(135deg, rgba(96, 184, 212, 0.7), rgba(55, 69, 181, 0.82));
animation-name: kbLoginCurtainBottomA;
animation-delay: 0.12s;
}
.kb-login-curtain-bottom-b {
background: linear-gradient(135deg, rgba(72, 125, 255, 0.5), rgba(36, 74, 179, 0.76));
animation-name: kbLoginCurtainBottomB;
animation-delay: 0.18s;
}
.kb-login-ambient {
.kb-login-backdrop {
position: absolute;
inset: 0;
background:
radial-gradient(circle at 50% 10%, rgba(255, 255, 255, 0.12), transparent 30%),
url("../img/desktop-glow.svg") center/cover no-repeat;
opacity: 0.9;
linear-gradient(180deg, rgba(255, 255, 255, 0.10), transparent 16%),
radial-gradient(circle at center, transparent 24%, rgba(7, 13, 8, 0.18) 100%);
pointer-events: none;
}
.kb-login-panel {
.kb-login-stage {
position: relative;
z-index: 1;
display: grid;
gap: 22px;
width: min(540px, 100%);
padding: 28px;
border-radius: 28px;
background: rgba(15, 23, 42, 0.62);
border: 1px solid rgba(255, 255, 255, 0.12);
box-shadow: 0 24px 80px rgba(2, 6, 23, 0.42);
backdrop-filter: blur(24px);
opacity: 0;
transform: translateY(28px) scale(0.98);
animation: kbLoginPanelReveal 0.72s cubic-bezier(0.22, 1, 0.36, 1) 0.35s forwards;
transition: transform 180ms ease, box-shadow 180ms ease, border-color 180ms ease;
justify-items: center;
align-content: start;
min-height: 100vh;
}
.kb-login-panel:hover,
.kb-login-panel:focus-within {
transform: translateY(-2px);
box-shadow: 0 30px 90px rgba(2, 6, 23, 0.5);
border-color: rgba(255, 255, 255, 0.18);
.kb-login-clock {
margin-top: 22px;
text-align: center;
color: #f8fafc;
text-shadow: 0 10px 28px rgba(0, 0, 0, 0.22);
}
.kb-login-copy {
.kb-login-date {
margin: 0 0 6px;
font-size: 1.15rem;
font-weight: 400;
letter-spacing: 0.01em;
opacity: 0.9;
}
.kb-login-time {
margin: 0;
font-size: clamp(3.8rem, 8vw, 5.6rem);
font-weight: 300;
line-height: 1;
}
.kb-login-card {
width: min(420px, 100%);
margin-top: 148px;
display: grid;
gap: 12px;
gap: 14px;
}
.kb-login-brand {
text-align: center;
}
.kb-login-kicker {
margin: 0;
font-size: 12px;
letter-spacing: 0.18em;
font-size: 0.76rem;
font-weight: 700;
letter-spacing: 0.22em;
text-transform: uppercase;
color: #cbd5e1;
color: rgba(248, 250, 252, 0.84);
}
.kb-login-title {
.kb-login-form-shell,
#kc-form-wrapper,
#kc-form {
margin: 0;
font-size: clamp(30px, 4vw, 40px);
line-height: 1.04;
color: #f8fafc;
}
.kb-login-subtitle {
margin: 0;
color: #dbe5f1;
line-height: 1.55;
}
.kb-login-form-shell {
margin: 0;
}
#kc-form-wrapper {
padding: 0 !important;
}
#kc-form-login input[type="text"],
#kc-form-login input[type="password"] {
#kc-form-login {
display: grid;
gap: 12px;
}
.kb-form-group {
display: grid;
gap: 6px;
}
.kb-text-input {
width: 100%;
min-height: 48px;
border-radius: 14px !important;
border: 1px solid rgba(255, 255, 255, 0.12) !important;
background: rgba(255, 255, 255, 0.08) !important;
padding: 0 18px;
border: 1px solid rgba(255, 255, 255, 0.10);
border-radius: 999px;
background: rgba(213, 223, 193, 0.54) !important;
color: #f8fafc !important;
box-shadow: none !important;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08) !important;
outline: none;
font-size: 1rem;
backdrop-filter: blur(9px);
transition: background-color 160ms ease, border-color 160ms ease;
}
#kc-form-login input::placeholder {
color: rgba(226, 232, 240, 0.72);
.kb-text-input:focus {
border-color: rgba(255, 255, 255, 0.26);
background: rgba(213, 223, 193, 0.62) !important;
}
#kc-form-login .pf-v5-c-button,
#kc-login {
min-height: 50px;
border: 0 !important;
border-radius: 14px !important;
background: linear-gradient(180deg, #a7f3d0, #6ee7b7) !important;
color: #082032 !important;
.kb-text-input::placeholder {
color: rgba(248, 250, 252, 0.82);
}
.kb-password-row {
position: relative;
}
.kb-text-input-password {
padding-right: 56px;
}
.kb-password-toggle {
position: absolute;
top: 50%;
right: 10px;
transform: translateY(-50%);
display: inline-flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
border: 0;
border-radius: 999px;
background: transparent;
color: rgba(248, 250, 252, 0.88);
cursor: pointer;
}
.kb-input-error {
padding: 0 10px;
font-size: 0.82rem;
color: #fee2e2;
}
.kb-form-meta {
display: grid;
gap: 8px;
margin-top: 2px;
}
.kb-remember {
display: inline-flex;
align-items: center;
gap: 8px;
color: rgba(248, 250, 252, 0.94);
font-size: 0.88rem;
cursor: pointer;
}
.kb-remember input {
width: 16px;
height: 16px;
accent-color: #93c5fd;
}
.kb-form-links a {
color: rgba(248, 250, 252, 0.94) !important;
text-decoration: none;
font-size: 0.88rem;
}
.kb-form-links a:hover,
.kb-registration a:hover {
text-decoration: underline;
}
.kb-form-submit {
margin-top: 4px;
}
.kb-submit-button,
.kb-register-button {
display: inline-flex;
align-items: center;
justify-content: center;
width: 100%;
min-height: 48px;
border: 0;
border-radius: 999px;
font-size: 0.98rem;
font-weight: 700;
box-shadow: none !important;
transition: transform 160ms ease, filter 160ms ease, background-color 160ms ease, border-color 160ms ease;
letter-spacing: 0.01em;
text-decoration: none;
cursor: pointer;
transition: transform 160ms ease, filter 160ms ease;
}
#kc-form-login .pf-v5-c-button:hover,
#kc-login:hover {
.kb-submit-button {
background: linear-gradient(180deg, #7dd3fc, #0ea5e9) !important;
color: #082f49 !important;
box-shadow: 0 14px 30px rgba(14, 165, 233, 0.22);
}
.kb-register-button {
background: rgba(248, 250, 252, 0.18);
color: #f8fafc !important;
border: 1px solid rgba(248, 250, 252, 0.16);
backdrop-filter: blur(8px);
}
.kb-submit-button:hover,
.kb-register-button:hover {
transform: translateY(-1px);
filter: brightness(1.03);
}
#kc-form-options {
padding-top: 4px;
.kb-login-info {
padding: 16px 18px;
border-radius: 18px;
background: rgba(9, 18, 12, 0.34);
border: 1px solid rgba(255, 255, 255, 0.08);
color: rgba(248, 250, 252, 0.94);
backdrop-filter: blur(10px);
}
#kc-form-options .checkbox label {
display: inline-flex;
align-items: center;
gap: 8px;
.kb-login-info strong {
display: block;
margin-bottom: 6px;
font-size: 0.95rem;
}
#kc-social-providers {
margin-top: 10px;
.kb-login-info p {
margin: 0;
font-size: 0.9rem;
line-height: 1.5;
}
#kc-social-providers hr {
border-color: rgba(255, 255, 255, 0.12);
}
#kc-registration {
margin-top: 18px;
}
.pf-v5-c-alert {
border-radius: 14px !important;
}
@keyframes kbLoginPanelReveal {
from {
opacity: 0;
transform: translateY(28px) scale(0.98);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
@keyframes kbLoginCurtainTopA {
from {
transform: rotate(46deg) translateX(0);
}
to {
transform: rotate(46deg) translateX(22vmax);
}
}
@keyframes kbLoginCurtainTopB {
from {
transform: rotate(136deg) translateX(0);
}
to {
transform: rotate(136deg) translateX(18vmax);
}
}
@keyframes kbLoginCurtainBottomA {
from {
transform: rotate(-44deg) translateX(0);
}
to {
transform: rotate(-44deg) translateX(22vmax);
}
}
@keyframes kbLoginCurtainBottomB {
from {
transform: rotate(-134deg) translateX(0);
}
to {
transform: rotate(-134deg) translateX(18vmax);
}
}
@media (prefers-reduced-motion: reduce) {
.kb-login-curtain,
.kb-login-panel {
animation: none;
}
.kb-login-panel {
opacity: 1;
transform: none;
}
.kb-registration {
width: min(420px, 100%);
margin: 14px auto 0;
}
@media (max-width: 640px) {
#kc-content-wrapper {
padding-top: 20px;
}
.kb-login-shell {
min-height: auto;
padding: 16px;
padding: 18px 14px 24px;
}
.kb-login-panel {
padding: 22px 18px;
border-radius: 22px;
.kb-login-clock {
margin-top: 10px;
}
.kb-login-time {
font-size: 3.7rem;
}
.kb-login-card {
margin-top: 88px;
}
}

View File

@@ -0,0 +1,22 @@
(function () {
const registerLink = document.getElementById('kb-register-button');
if (!registerLink) {
return;
}
const params = new URLSearchParams(window.location.search);
const redirectUri = params.get('redirect_uri');
if (!redirectUri) {
return;
}
try {
const target = new URL(redirectUri);
registerLink.href = target.origin + '/auth/register';
registerLink.hidden = false;
} catch (error) {
console.warn('Registrierungslink konnte nicht vorbereitet werden.', error);
}
}());

View File

@@ -0,0 +1,26 @@
(function () {
const dateNode = document.getElementById('kb-login-date');
const timeNode = document.getElementById('kb-login-time');
if (!dateNode || !timeNode) {
return;
}
const renderClock = () => {
const now = new Date();
dateNode.textContent = new Intl.DateTimeFormat(document.documentElement.lang || 'de', {
weekday: 'long',
month: 'long',
day: 'numeric',
}).format(now);
timeNode.textContent = new Intl.DateTimeFormat(document.documentElement.lang || 'de', {
hour: 'numeric',
minute: '2-digit',
}).format(now);
};
renderClock();
window.setInterval(renderClock, 1000);
}());