feat(theme): add social login button styling and legal footer
All checks were successful
CI / Format (push) Successful in 25s
CI / Clippy (push) Successful in 2m25s
CI / Security Audit (push) Has been skipped
CI / Tests (push) Has been skipped
CI / Deploy (push) Has been skipped

Style social identity provider buttons (GitHub, Microsoft, Google) as
full-width stacked buttons matching the dark theme. Add footer.js to
inject Privacy Policy and Impressum links below the login card. Fix
PF3 background image bleeding through on html element.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Sharang Parnerkar
2026-02-21 14:09:58 +01:00
parent 50237f5377
commit e6b2dfe19b
3 changed files with 410 additions and 15 deletions

View File

@@ -75,6 +75,7 @@
/* ===== Base Page ===== */ /* ===== Base Page ===== */
html.login-pf { html.login-pf {
background-color: var(--cai-bg-body) !important; background-color: var(--cai-bg-body) !important;
background-image: none !important;
} }
html.login-pf body { html.login-pf body {
@@ -469,38 +470,146 @@ input.pf-c-button.pf-m-primary:hover,
/* ===== Social Login / Identity Providers ===== */ /* ===== Social Login / Identity Providers ===== */
#kc-social-providers { #kc-social-providers {
margin-top: 20px; margin-top: 24px !important;
padding-top: 20px; padding-top: 20px !important;
border-top: 1px solid var(--cai-border-primary); border-top: 1px solid var(--cai-border-primary) !important;
} }
#kc-social-providers ul { /* Social <hr> separator */
list-style: none; #kc-social-providers > hr {
padding: 0; border: none !important;
margin: 0; border-top: 1px solid var(--cai-border-primary) !important;
margin: 0 0 16px 0 !important;
} }
/* "Or sign in with" heading - subtle divider text */
#kc-social-providers h2,
#kc-social-providers > h2,
#kc-social-providers h4 {
font-family: 'Inter', sans-serif !important;
font-size: 12px !important;
font-weight: 500 !important;
color: var(--cai-text-faint) !important;
text-transform: uppercase !important;
letter-spacing: 0.08em !important;
text-align: center !important;
margin: 0 0 16px 0 !important;
padding: 0 !important;
}
/* Social button list - stacked full-width
* Production uses: ul.pf-c-login__main-footer-links.kc-social-links
* PF4 sets flex-direction:row on this - we must override with high specificity */
#kc-social-providers ul.pf-c-login__main-footer-links,
#kc-social-providers ul.kc-social-links,
#kc-social-providers ul,
#kc-social-providers ol {
list-style: none !important;
padding: 0 !important;
margin: 0 !important;
display: flex !important;
flex-direction: column !important;
gap: 10px !important;
width: 100% !important;
}
#kc-social-providers ul.pf-c-login__main-footer-links > li,
#kc-social-providers ul.kc-social-links > li,
#kc-social-providers li { #kc-social-providers li {
margin-bottom: 8px; margin: 0 !important;
padding: 0 !important;
width: 100% !important;
flex: none !important;
display: block !important;
} }
/* Social login buttons - full-width stacked with icon + label
* Production uses: a.pf-c-button.pf-m-control.pf-m-block.kc-social-item
* Must override .pf-c-button.pf-m-control (password toggle uses same classes) */
#kc-social-providers a.pf-c-button.pf-m-control,
#kc-social-providers a.kc-social-item,
#kc-social-providers a.pf-m-block,
#kc-social-providers a, #kc-social-providers a,
#kc-social-providers .pf-c-button { #kc-social-providers .zocial {
background-color: var(--cai-bg-surface) !important; background-color: var(--cai-bg-input) !important;
border: 1px solid var(--cai-border-secondary) !important; border: 1px solid var(--cai-border-secondary) !important;
border-top: 1px solid var(--cai-border-secondary) !important;
border-left: 1px solid var(--cai-border-secondary) !important;
border-radius: 8px !important; border-radius: 8px !important;
color: var(--cai-text-primary) !important; color: var(--cai-text-primary) !important;
padding: 10px 16px !important; padding: 12px 16px !important;
display: block; display: flex !important;
text-align: center; align-items: center !important;
justify-content: center !important;
gap: 10px !important;
width: 100% !important;
box-sizing: border-box !important;
text-align: center !important;
font-family: 'Inter', sans-serif !important;
font-size: 14px !important; font-size: 14px !important;
font-weight: 500 !important; font-weight: 500 !important;
transition: border-color 0.15s ease !important; text-decoration: none !important;
transition: border-color 0.2s ease, background-color 0.2s ease,
box-shadow 0.2s ease, transform 0.15s ease !important;
white-space: nowrap !important;
} }
#kc-social-providers a.pf-c-button.pf-m-control:hover,
#kc-social-providers a.kc-social-item:hover,
#kc-social-providers a:hover, #kc-social-providers a:hover,
#kc-social-providers .pf-c-button:hover { #kc-social-providers .zocial:hover {
border-color: var(--cai-accent) !important; border-color: var(--cai-accent) !important;
background-color: rgba(145, 164, 210, 0.06) !important;
box-shadow: 0 0 16px rgba(145, 164, 210, 0.12) !important;
color: var(--cai-text-heading) !important;
transform: translateY(-1px) !important;
}
/* Provider icons inside social buttons */
#kc-social-providers .kc-social-provider-logo,
#kc-social-providers i.fa,
#kc-social-providers .kc-social-icon-text {
color: var(--cai-accent) !important;
font-size: 16px !important;
flex-shrink: 0 !important;
}
/* Provider text label */
#kc-social-providers .kc-social-provider-name {
font-family: 'Inter', sans-serif !important;
font-size: 14px !important;
font-weight: 500 !important;
}
/* Grid layout for social providers (some themes use .kc-social-grid) */
.kc-social-grid {
display: flex !important;
flex-direction: column !important;
gap: 10px !important;
}
.kc-social-grid > div {
width: 100% !important;
max-width: none !important;
flex: none !important;
}
/* PF v5 grid layout override */
.pf-v5-l-grid.pf-m-gutter {
display: flex !important;
flex-direction: column !important;
gap: 10px !important;
}
.pf-v5-l-grid__item {
width: 100% !important;
max-width: none !important;
flex: none !important;
}
/* Social section separator */
#kc-social-providers::before {
content: none;
} }
/* ===== Form Buttons Row ===== */ /* ===== Form Buttons Row ===== */
@@ -581,3 +690,244 @@ input.pf-c-button.pf-m-primary:hover,
max-width: 440px; max-width: 440px;
margin: 0 auto !important; margin: 0 auto !important;
} }
/* ===== Legal Footer (injected by footer.js) ===== */
.cai-legal-footer {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
margin-top: 24px;
padding: 0;
}
.cai-legal-link {
font-family: 'Inter', sans-serif;
font-size: 12px;
color: var(--cai-text-faint) !important;
text-decoration: none !important;
transition: color 0.15s ease;
}
.cai-legal-link:hover {
color: var(--cai-text-muted) !important;
}
.cai-legal-sep {
font-size: 10px;
color: var(--cai-text-faint);
opacity: 0.5;
}
/* ===== PF v5 Social Provider Overrides ===== */
/* Production may use keycloak.v2 (PF v5) classes */
.pf-v5-c-login__main-footer {
padding: 0 32px 28px !important;
background: transparent !important;
}
.pf-v5-c-login__main-footer-band {
background-color: var(--cai-bg-surface) !important;
border-top: 1px solid var(--cai-border-primary) !important;
padding: 16px 32px !important;
text-align: center !important;
border-radius: 0 0 12px 12px !important;
}
.pf-v5-c-login__main-footer-band-item {
font-size: 14px !important;
color: var(--cai-text-muted) !important;
}
/* PF v5 social buttons */
.pf-v5-c-login__main-footer-links-item a,
.pf-v5-c-button.pf-m-secondary.pf-m-block {
background-color: var(--cai-bg-input) !important;
border: 1px solid var(--cai-border-secondary) !important;
border-radius: 8px !important;
color: var(--cai-text-primary) !important;
font-family: 'Inter', sans-serif !important;
font-size: 14px !important;
font-weight: 500 !important;
padding: 12px 16px !important;
width: 100% !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
gap: 10px !important;
transition: border-color 0.2s ease, background-color 0.2s ease,
box-shadow 0.2s ease, transform 0.15s ease !important;
}
.pf-v5-c-login__main-footer-links-item a:hover,
.pf-v5-c-button.pf-m-secondary.pf-m-block:hover {
border-color: var(--cai-accent) !important;
background-color: rgba(145, 164, 210, 0.06) !important;
box-shadow: 0 0 16px rgba(145, 164, 210, 0.12) !important;
color: var(--cai-text-heading) !important;
transform: translateY(-1px) !important;
}
/* PF v5 social footer links list - stacked */
.pf-v5-c-login__main-footer-links {
display: flex !important;
flex-direction: column !important;
gap: 10px !important;
list-style: none !important;
padding: 0 !important;
}
.pf-v5-c-login__main-footer-links-item {
width: 100% !important;
}
/* PF v5 main container and card */
.pf-v5-c-login {
background:
radial-gradient(ellipse at 20% 20%, rgba(75, 63, 224, 0.07) 0%, transparent 50%),
radial-gradient(ellipse at 80% 80%, rgba(56, 178, 172, 0.05) 0%, transparent 50%),
radial-gradient(ellipse at 50% 50%, rgba(109, 133, 198, 0.03) 0%, transparent 70%),
var(--cai-bg-body) !important;
background-size: 200% 200%, 200% 200%, 100% 100%, 100% 100% !important;
animation: ambientShift 20s ease-in-out infinite !important;
}
.pf-v5-c-login::before {
background-image: none !important;
}
.pf-v5-c-login__container {
max-width: 440px !important;
}
.pf-v5-c-login__header {
text-align: center !important;
margin-bottom: 32px !important;
}
.pf-v5-c-brand {
font-family: 'Space Grotesk', sans-serif !important;
font-size: 28px !important;
font-weight: 700 !important;
color: var(--cai-text-heading) !important;
}
.pf-v5-c-login__main {
background-color: var(--cai-bg-card) !important;
border: 1px solid var(--cai-border-secondary) !important;
border-radius: 12px !important;
animation: cardGlow 6s ease-in-out infinite !important;
overflow: hidden !important;
position: relative !important;
}
.pf-v5-c-login__main::before {
content: '' !important;
position: absolute !important;
top: 0 !important;
left: 0 !important;
right: 0 !important;
height: 2px !important;
background: linear-gradient(90deg, transparent, var(--cai-brand-indigo),
var(--cai-brand-teal), var(--cai-accent-secondary), transparent) !important;
opacity: 0.5 !important;
}
.pf-v5-c-login__main-header {
padding: 28px 32px 0 !important;
background: transparent !important;
}
.pf-v5-c-login__main-header .pf-v5-c-title {
font-family: 'Space Grotesk', sans-serif !important;
font-size: 22px !important;
font-weight: 600 !important;
color: var(--cai-text-heading) !important;
}
.pf-v5-c-login__main-body {
padding: 24px 32px !important;
}
/* PF v5 form controls */
.pf-v5-c-form-control {
background-color: var(--cai-bg-input) !important;
border: 1px solid var(--cai-border-secondary) !important;
border-radius: 8px !important;
color: var(--cai-text-primary) !important;
font-family: 'Inter', sans-serif !important;
font-size: 14px !important;
}
.pf-v5-c-form-control:focus-within {
border-color: var(--cai-accent) !important;
box-shadow: 0 0 0 1px var(--cai-accent), 0 0 12px rgba(145, 164, 210, 0.1) !important;
}
.pf-v5-c-form__label-text {
font-family: 'Inter', sans-serif !important;
font-size: 13px !important;
font-weight: 500 !important;
color: var(--cai-text-muted) !important;
}
/* PF v5 primary button */
.pf-v5-c-button.pf-m-primary {
background: linear-gradient(135deg, var(--cai-accent), var(--cai-accent-secondary),
var(--cai-brand-indigo), var(--cai-accent-secondary), var(--cai-accent)) !important;
background-size: 300% 100% !important;
animation: buttonShimmer 6s ease-in-out infinite !important;
border: none !important;
border-radius: 8px !important;
color: #0a0c10 !important;
font-family: 'Inter', sans-serif !important;
font-size: 14px !important;
font-weight: 600 !important;
box-shadow: 0 2px 12px rgba(109, 133, 198, 0.2) !important;
}
.pf-v5-c-button.pf-m-primary:hover {
opacity: 0.95;
box-shadow: 0 4px 20px rgba(109, 133, 198, 0.35) !important;
}
/* PF v5 links */
.pf-v5-c-login a,
.pf-v5-c-login__main a,
.pf-v5-c-button.pf-m-link {
color: var(--cai-accent) !important;
text-decoration: none !important;
}
.pf-v5-c-login a:hover,
.pf-v5-c-button.pf-m-link:hover {
color: var(--cai-accent-secondary) !important;
}
/* PF v5 alerts */
.pf-v5-c-alert.pf-m-inline {
background-color: var(--cai-bg-surface) !important;
border: 1px solid var(--cai-border-secondary) !important;
border-radius: 8px !important;
color: var(--cai-text-primary) !important;
}
/* PF v5 input group (password) */
.pf-v5-c-input-group {
background: transparent !important;
border-radius: 8px !important;
overflow: hidden !important;
}
.pf-v5-c-button.pf-m-control {
background-color: var(--cai-bg-surface) !important;
color: var(--cai-text-muted) !important;
border: 1px solid var(--cai-border-secondary) !important;
border-left: 1px solid var(--cai-border-primary) !important;
border-radius: 0 8px 8px 0 !important;
}
.pf-v5-c-button.pf-m-control:hover {
color: var(--cai-accent) !important;
background-color: rgba(145, 164, 210, 0.08) !important;
}

View File

@@ -0,0 +1,44 @@
/**
* CERTifAI Keycloak Theme - Footer Injection
*
* Injects legal footer links (Privacy Policy, Impressum) below the login card.
* Uses the APP_BASE_URL from the page's redirect_uri to construct absolute links,
* falling back to relative paths if unavailable.
*/
(function () {
"use strict";
document.addEventListener("DOMContentLoaded", function () {
// Derive the app base URL from the OAuth redirect_uri parameter
var appBase = "";
try {
var params = new URLSearchParams(window.location.search);
var redirectUri = params.get("redirect_uri");
if (redirectUri) {
var url = new URL(redirectUri);
appBase = url.origin;
}
} catch (_) {
// Ignore parse errors; links will be relative
}
// Build the footer element
var footer = document.createElement("div");
footer.className = "cai-legal-footer";
footer.innerHTML =
'<a href="' + appBase + '/privacy" class="cai-legal-link" target="_blank" rel="noopener">' +
"Privacy Policy" +
"</a>" +
'<span class="cai-legal-sep">|</span>' +
'<a href="' + appBase + '/impressum" class="cai-legal-link" target="_blank" rel="noopener">' +
"Impressum" +
"</a>";
// Insert after the card or at the end of .login-pf-page / .pf-v5-c-login__container
var card = document.querySelector(".card-pf") ||
document.querySelector(".pf-v5-c-login__main");
if (card && card.parentNode) {
card.parentNode.insertBefore(footer, card.nextSibling);
}
});
})();

View File

@@ -1,3 +1,4 @@
parent=keycloak parent=keycloak
import=common/keycloak import=common/keycloak
styles=css/login.css styles=css/login.css
scripts=js/footer.js