livrare lot 2

This commit is contained in:
EVOTECH IT SRL 2026-07-10 03:39:53 -07:00
commit 8ecc78e729
763 changed files with 164593 additions and 0 deletions

View file

@ -0,0 +1,50 @@
<#assign customVerifyUrl = "https://didi365.eu/api/auth/verify-email?key=" + link?keep_after("key=")>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Verifică adresa de email</title>
</head>
<body style="margin: 0; padding: 0; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background-color: #f4f4f4;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="background-color: #f4f4f4;">
<tr>
<td align="center" style="padding: 40px 20px;">
<table role="presentation" width="600" cellspacing="0" cellpadding="0" style="background-color: #ffffff; border-radius: 12px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);">
<tr>
<td style="background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); padding: 40px 40px 30px; border-radius: 12px 12px 0 0; text-align: center;">
<h1 style="color: #ffffff; margin: 0; font-size: 28px; font-weight: 600;">DIDI</h1>
<p style="color: #a0a0a0; margin: 8px 0 0; font-size: 14px;">Misinformation Detection Platform</p>
</td>
</tr>
<tr>
<td style="padding: 40px;">
<h2 style="color: #1a1a2e; margin: 0 0 20px; font-size: 22px; font-weight: 600;">Verifică adresa de email</h2>
<p style="color: #555555; font-size: 16px; line-height: 1.6; margin: 0 0 25px;">Salut <strong>${user.firstName!""}</strong>,</p>
<p style="color: #555555; font-size: 16px; line-height: 1.6; margin: 0 0 25px;">Mulțumim pentru înregistrare! Pentru a activa contul tău DIDI, te rugăm să confirmi adresa de email apăsând butonul de mai jos:</p>
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="margin: 30px 0;">
<tr>
<td align="center">
<a href="${customVerifyUrl}" target="_blank" style="display: inline-block; background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: #ffffff; text-decoration: none; padding: 16px 40px; border-radius: 8px; font-size: 16px; font-weight: 600; box-shadow: 0 4px 12px rgba(76, 175, 80, 0.3);">Verifică Email</a>
</td>
</tr>
</table>
<p style="color: #888888; font-size: 14px; line-height: 1.6; margin: 25px 0 0;">Sau copiază acest link în browser:</p>
<p style="color: #4CAF50; font-size: 13px; word-break: break-all; background-color: #f8f8f8; padding: 12px; border-radius: 6px; margin: 10px 0 25px;">${customVerifyUrl}</p>
<p style="color: #888888; font-size: 14px; line-height: 1.6; margin: 0;">Link-ul expiră în <strong>${linkExpiration}</strong>.</p>
<hr style="border: none; border-top: 1px solid #eeeeee; margin: 30px 0;">
<p style="color: #999999; font-size: 13px; line-height: 1.6; margin: 0;">Dacă nu ai creat un cont pe DIDI, poți ignora acest email.</p>
</td>
</tr>
<tr>
<td style="background-color: #f8f8f8; padding: 25px 40px; border-radius: 0 0 12px 12px; text-align: center;">
<p style="color: #999999; font-size: 12px; margin: 0 0 8px;">&copy; 2025 didi - Misinformation Detection Platform</p>
<p style="color: #bbbbbb; font-size: 11px; margin: 0;">Acest email a fost trimis automat. Te rugăm să nu răspunzi.</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

View file

@ -0,0 +1,50 @@
<#assign customVerifyUrl = "https://didi365.eu/api/auth/verify-email?key=" + link?keep_after("key=")>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Verifică adresa de email</title>
</head>
<body style="margin: 0; padding: 0; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background-color: #050510;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="background-color: #050510;">
<tr>
<td align="center" style="padding: 40px 20px;">
<table role="presentation" width="600" cellspacing="0" cellpadding="0" style="background-color: #0f0f1a; border-radius: 16px; box-shadow: 0 8px 32px rgba(124, 58, 237, 0.3); border: 1px solid rgba(124, 58, 237, 0.2);">
<tr>
<td style="background: linear-gradient(135deg, #7c3aed 0%, #4f46e5 100%); padding: 40px 40px 30px; border-radius: 16px 16px 0 0; text-align: center;">
<h1 style="color: #ffffff; margin: 0; font-size: 36px; font-weight: 700; letter-spacing: 2px;">didi</h1>
<p style="color: rgba(255,255,255,0.7); margin: 8px 0 0; font-size: 14px; letter-spacing: 1px;">Misinformation Detection Platform</p>
</td>
</tr>
<tr>
<td style="padding: 40px;">
<h2 style="color: #E8E8E8; margin: 0 0 20px; font-size: 22px; font-weight: 600;">Verifică adresa de email</h2>
<p style="color: #D1D1D1; font-size: 16px; line-height: 1.6; margin: 0 0 25px;">Salut <strong style="color: #00d4ff;">${user.firstName!""}</strong>,</p>
<p style="color: #D1D1D1; font-size: 16px; line-height: 1.6; margin: 0 0 25px;">Pentru a-ți activa contul în platforma <strong style="color: #7c3aed;">didi</strong>, te rugăm să confirmi adresa de email apăsând butonul de mai jos:</p>
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="margin: 30px 0;">
<tr>
<td align="center">
<a href="${customVerifyUrl}" target="_blank" style="display: inline-block; background: linear-gradient(135deg, #7c3aed 0%, #4f46e5 100%); color: #ffffff; text-decoration: none; padding: 16px 48px; border-radius: 12px; font-size: 16px; font-weight: 600; box-shadow: 0 4px 20px rgba(124, 58, 237, 0.4); letter-spacing: 0.5px;">Verifică Email</a>
</td>
</tr>
</table>
<p style="color: #9CA3AF; font-size: 14px; line-height: 1.6; margin: 25px 0 0;">Sau copiază acest link în browser:</p>
<p style="color: #00d4ff; font-size: 12px; word-break: break-all; background-color: rgba(124, 58, 237, 0.1); padding: 14px; border-radius: 8px; margin: 10px 0 25px; border: 1px solid rgba(124, 58, 237, 0.2);">${customVerifyUrl}</p>
<p style="color: #9CA3AF; font-size: 14px; line-height: 1.6; margin: 0;">Link-ul expiră în <strong style="color: #E8E8E8;"><#if linkExpiration?number gt 60>${(linkExpiration?number / 60)?round} minute<#else>${linkExpiration} secunde</#if></strong>.</p>
<hr style="border: none; border-top: 1px solid rgba(124, 58, 237, 0.2); margin: 30px 0;">
<p style="color: #6B7280; font-size: 13px; line-height: 1.6; margin: 0;">Dacă nu ai solicitat această acțiune, poți ignora acest email.</p>
</td>
</tr>
<tr>
<td style="background-color: rgba(124, 58, 237, 0.05); padding: 25px 40px; border-radius: 0 0 16px 16px; text-align: center; border-top: 1px solid rgba(124, 58, 237, 0.1);">
<p style="color: #9CA3AF; font-size: 12px; margin: 0 0 8px;">&copy; 2025 <span style="color: #7c3aed; font-weight: 600;">didi</span> - Misinformation Detection Platform</p>
<p style="color: #6B7280; font-size: 11px; margin: 0;">Acest email a fost trimis automat. Te rugăm să nu răspunzi.</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

View file

@ -0,0 +1,14 @@
Salut ${user.firstName!""},
Mulțumim pentru înregistrare pe DIDI!
Pentru a activa contul tău, te rugăm să accesezi link-ul de mai jos:
${link}
Link-ul expiră în ${linkExpiration}.
Dacă nu ai creat un cont pe DIDI, poți ignora acest email.
---
DIDI - Misinformation Detection Platform

View file

@ -0,0 +1,117 @@
<#import "template.ftl" as layout>
<@layout.registrationLayout displayMessage=true; section>
<#if section = "header">
<#if messageHeader??>
${messageHeader}
<#else>
${message.summary}
</#if>
<#elseif section = "form">
<div id="kc-info-message">
<p class="instruction">${message.summary}<#if requiredActions??><#list requiredActions>: <b><#items as reqActionItem>${kcSanitize(msg("requiredAction.${reqActionItem}"))?no_esc}<#sep>, </#items></b></#list><#else></#if></p>
<#if skipLink??>
<#else>
<#-- Mobile app deep link redirect -->
<#if client?? && client.clientId?? && client.clientId == "didi-mobile-app">
<#assign mobileRedirectUrl = "didi://email-verified">
<p style="text-align: center; margin-top: 20px;">
<a href="${mobileRedirectUrl}" style="display: inline-block; background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: #ffffff; text-decoration: none; padding: 14px 32px; border-radius: 8px; font-size: 16px; font-weight: 600;">
Deschide aplicația DIDI
</a>
</p>
<p style="color: #666; font-size: 14px; text-align: center; margin-top: 15px;">
Vei fi redirecționat automat către aplicație...
</p>
<script>
// Try to redirect to mobile app after 1.5 seconds
setTimeout(function() {
window.location.href = "${mobileRedirectUrl}";
}, 1500);
// Fallback: if deep link fails, try alternative scheme
setTimeout(function() {
window.location.href = "com.didi365.app://email-verified";
}, 3000);
</script>
<#-- Web app redirect -->
<#elseif client?? && client.clientId?? && client.clientId == "didi-web-app">
<#assign webRedirectUrl = "/email-verified">
<p style="text-align: center; margin-top: 20px;">
<a href="${webRedirectUrl}" style="display: inline-block; background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: #ffffff; text-decoration: none; padding: 14px 32px; border-radius: 8px; font-size: 16px; font-weight: 600;">
Continuă către aplicație
</a>
</p>
<p style="color: #666; font-size: 14px; text-align: center; margin-top: 15px;">
Vei fi redirecționat automat...
</p>
<script>
setTimeout(function() {
window.location.href = "${webRedirectUrl}";
}, 2000);
</script>
<#-- Standard Keycloak redirects -->
<#elseif pageRedirectUri?has_content>
<p><a href="${pageRedirectUri}">${kcSanitize(msg("backToApplication"))?no_esc}</a></p>
<script>
setTimeout(function() {
window.location.href = "${pageRedirectUri}";
}, 2000);
</script>
<#elseif actionUri?has_content>
<p><a href="${actionUri}">${kcSanitize(msg("proceedWithAction"))?no_esc}</a></p>
<script>
setTimeout(function() {
window.location.href = "${actionUri}";
}, 2000);
</script>
<#elseif (client.baseUrl)?has_content>
<p><a href="${client.baseUrl}">${kcSanitize(msg("backToApplication"))?no_esc}</a></p>
<script>
setTimeout(function() {
window.location.href = "${client.baseUrl}";
}, 2000);
</script>
<#-- Fallback: detect mobile browser and redirect accordingly -->
<#else>
<div id="redirect-buttons" style="text-align: center; margin-top: 20px;">
<p style="color: #666; font-size: 14px; margin-bottom: 15px;">
Alege cum dorești să continui:
</p>
<p>
<a href="didi://email-verified" id="mobile-link" style="display: inline-block; background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: #ffffff; text-decoration: none; padding: 14px 32px; border-radius: 8px; font-size: 16px; font-weight: 600; margin: 5px;">
📱 Deschide în aplicație
</a>
</p>
<p>
<a href="/email-verified" id="web-link" style="display: inline-block; background: linear-gradient(135deg, #2196F3 0%, #1976D2 100%); color: #ffffff; text-decoration: none; padding: 14px 32px; border-radius: 8px; font-size: 16px; font-weight: 600; margin: 5px;">
🌐 Continuă în browser
</a>
</p>
</div>
<script>
(function() {
var ua = navigator.userAgent || navigator.vendor || window.opera;
var isMobile = /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(ua.toLowerCase());
if (isMobile) {
// On mobile, try deep link first
setTimeout(function() {
window.location.href = "didi://email-verified";
}, 1500);
// Fallback to alternative scheme
setTimeout(function() {
window.location.href = "com.didi365.app://email-verified";
}, 3000);
} else {
// On desktop, redirect to web app
setTimeout(function() {
window.location.href = "/email-verified";
}, 2000);
}
})();
</script>
</#if>
</#if>
</div>
</#if>
</@layout.registrationLayout>

View file

@ -0,0 +1,42 @@
<#import "template.ftl" as layout>
<@layout.registrationLayout displayInfo=true displayMessage=!messagesPerField.existsError('username'); section>
<#if section = "header">
${msg("emailForgotTitle")}
<#elseif section = "form">
<form id="kc-reset-password-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
<div class="back-link">
<a href="${url.loginUrl}">${msg("backToLogin")}</a>
</div>
<div class="${properties.kcFormGroupClass!}">
<div class="${properties.kcLabelWrapperClass!}">
<label for="username" class="${properties.kcLabelClass!}">
<#if !realm.loginWithEmailAllowed>${msg("username")}<#elseif !realm.registrationEmailAsUsername>${msg("usernameOrEmail")}<#else>${msg("email")}</#if>
</label>
</div>
<div class="${properties.kcInputWrapperClass!}">
<#if auth?has_content && auth.showUsername()>
<input type="text" id="username" name="username" class="${properties.kcInputClass!}" autofocus value="${auth.attemptedUsername}" aria-invalid="<#if messagesPerField.existsError('username')>true</#if>"/>
<#else>
<input type="text" id="username" name="username" class="${properties.kcInputClass!}" autofocus aria-invalid="<#if messagesPerField.existsError('username')>true</#if>"/>
</#if>
<#if messagesPerField.existsError('username')>
<span id="input-error-username" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
${kcSanitize(messagesPerField.get('username'))?no_esc}
</span>
</#if>
</div>
</div>
<div class="${properties.kcFormGroupClass!}">
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doSubmit")}"/>
</div>
</div>
</form>
<#elseif section = "info" >
<#if realm.duplicateEmailsAllowed>
${msg("emailInstructionUsername")}
<#else>
${msg("emailInstruction")}
</#if>
</#if>
</@layout.registrationLayout>

View file

@ -0,0 +1,25 @@
<#import "template.ftl" as layout>
<@layout.registrationLayout displayInfo=true displayMessage=false; section>
<#if section = "header">
${msg("emailVerifyTitle")}
<#elseif section = "form">
<div class="verify-email-box">
<p class="instruction">
${msg("emailVerifyInstruction1")}
</p>
<p class="instruction auto-check">
<span id="check-status">${msg("emailVerifyAutoCheck")}</span>
<span class="loader"></span>
</p>
</div>
<script>
setTimeout(function() { location.reload(); }, 5000);
</script>
<#elseif section = "info">
<p class="instruction">
${msg("emailVerifyInstruction3")}
<br/><br/>
<a href="${url.loginAction}">${msg("doClickHere")}</a> ${msg("emailVerifyInstruction2")}
</p>
</#if>
</@layout.registrationLayout>

View file

@ -0,0 +1,33 @@
# DIDI Custom Messages
# Forgot Password
emailForgotTitle=Reset your password
emailInstruction=Enter your email address and we will send you instructions to reset your password.
# Labels - hide them, use placeholders instead
usernameOrEmail=Email
username=Email
email=Email
password=Password
passwordConfirm=Confirm Password
firstName=First Name
lastName=Last Name
# Login
loginTitle=Sign in
doLogIn=Sign in
loginAccountTitle=Sign in to your account
# Register
registerTitle=Create account
doRegister=Create account
# Back link - keep it simple
backToLogin=\u2190 Back to sign in
# Email Verification
emailVerifyTitle=Verify your email
emailVerifyInstruction1=You need to verify your email address to activate your account.
emailVerifyInstruction2=to re-send the email.
emailVerifyInstruction3=Haven't received a verification code in your email?
emailVerifyAutoCheck=Checking verification status...

View file

@ -0,0 +1,27 @@
<#macro termsAcceptance>
<#if termsAcceptanceRequired??>
<div class="form-group">
<div class="${properties.kcInputWrapperClass!}">
${msg("termsTitle")}
<div id="kc-registration-terms-text">
${kcSanitize(msg("termsText"))?no_esc}
</div>
</div>
</div>
<div class="form-group">
<div class="${properties.kcLabelWrapperClass!}">
<input type="checkbox" id="termsAccepted" name="termsAccepted" class="${properties.kcCheckboxInputClass!}"
aria-invalid="<#if messagesPerField.existsError('termsAccepted')>true</#if>"
/>
<label for="termsAccepted" class="${properties.kcLabelClass!}">${msg("acceptTerms")}</label>
</div>
<#if messagesPerField.existsError('termsAccepted')>
<div class="${properties.kcLabelWrapperClass!}">
<span id="input-error-terms-accepted" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
${kcSanitize(messagesPerField.get('termsAccepted'))?no_esc}
</span>
</div>
</#if>
</div>
</#if>
</#macro>

View file

@ -0,0 +1,135 @@
<#import "template.ftl" as layout>
<#import "register-commons.ftl" as registerCommons>
<@layout.registrationLayout displayMessage=!messagesPerField.existsError('firstName','lastName','email','username','password','password-confirm','termsAccepted'); section>
<#if section = "header">
${msg("registerTitle")}
<#elseif section = "form">
<form id="kc-register-form" class="${properties.kcFormClass!}" action="${url.registrationAction}" method="post">
<div class="back-link">
<a href="${url.loginUrl}">${kcSanitize(msg("backToLogin"))?no_esc}</a>
</div>
<div class="${properties.kcFormGroupClass!}">
<div class="${properties.kcLabelWrapperClass!}">
<label for="firstName" class="${properties.kcLabelClass!}">${msg("firstName")}</label>
</div>
<div class="${properties.kcInputWrapperClass!}">
<input type="text" id="firstName" class="${properties.kcInputClass!}" name="firstName"
value="${(register.formData.firstName!'')}"
aria-invalid="<#if messagesPerField.existsError('firstName')>true</#if>"
/>
<#if messagesPerField.existsError('firstName')>
<span id="input-error-firstname" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
${kcSanitize(messagesPerField.get('firstName'))?no_esc}
</span>
</#if>
</div>
</div>
<div class="${properties.kcFormGroupClass!}">
<div class="${properties.kcLabelWrapperClass!}">
<label for="lastName" class="${properties.kcLabelClass!}">${msg("lastName")}</label>
</div>
<div class="${properties.kcInputWrapperClass!}">
<input type="text" id="lastName" class="${properties.kcInputClass!}" name="lastName"
value="${(register.formData.lastName!'')}"
aria-invalid="<#if messagesPerField.existsError('lastName')>true</#if>"
/>
<#if messagesPerField.existsError('lastName')>
<span id="input-error-lastname" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
${kcSanitize(messagesPerField.get('lastName'))?no_esc}
</span>
</#if>
</div>
</div>
<div class="${properties.kcFormGroupClass!}">
<div class="${properties.kcLabelWrapperClass!}">
<label for="email" class="${properties.kcLabelClass!}">${msg("email")}</label>
</div>
<div class="${properties.kcInputWrapperClass!}">
<input type="text" id="email" class="${properties.kcInputClass!}" name="email"
value="${(register.formData.email!'')}" autocomplete="email"
aria-invalid="<#if messagesPerField.existsError('email')>true</#if>"
/>
<#if messagesPerField.existsError('email')>
<span id="input-error-email" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
${kcSanitize(messagesPerField.get('email'))?no_esc}
</span>
</#if>
</div>
</div>
<#if !realm.registrationEmailAsUsername>
<div class="${properties.kcFormGroupClass!}">
<div class="${properties.kcLabelWrapperClass!}">
<label for="username" class="${properties.kcLabelClass!}">${msg("username")}</label>
</div>
<div class="${properties.kcInputWrapperClass!}">
<input type="text" id="username" class="${properties.kcInputClass!}" name="username"
value="${(register.formData.username!'')}" autocomplete="username"
aria-invalid="<#if messagesPerField.existsError('username')>true</#if>"
/>
<#if messagesPerField.existsError('username')>
<span id="input-error-username" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
${kcSanitize(messagesPerField.get('username'))?no_esc}
</span>
</#if>
</div>
</div>
</#if>
<#if passwordRequired??>
<div class="${properties.kcFormGroupClass!}">
<div class="${properties.kcLabelWrapperClass!}">
<label for="password" class="${properties.kcLabelClass!}">${msg("password")}</label>
</div>
<div class="${properties.kcInputWrapperClass!}">
<input type="password" id="password" class="${properties.kcInputClass!}" name="password"
autocomplete="new-password"
aria-invalid="<#if messagesPerField.existsError('password','password-confirm')>true</#if>"
/>
<#if messagesPerField.existsError('password')>
<span id="input-error-password" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
${kcSanitize(messagesPerField.get('password'))?no_esc}
</span>
</#if>
</div>
</div>
<div class="${properties.kcFormGroupClass!}">
<div class="${properties.kcLabelWrapperClass!}">
<label for="password-confirm" class="${properties.kcLabelClass!}">${msg("passwordConfirm")}</label>
</div>
<div class="${properties.kcInputWrapperClass!}">
<input type="password" id="password-confirm" class="${properties.kcInputClass!}"
name="password-confirm"
aria-invalid="<#if messagesPerField.existsError('password-confirm')>true</#if>"
/>
<#if messagesPerField.existsError('password-confirm')>
<span id="input-error-password-confirm" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
${kcSanitize(messagesPerField.get('password-confirm'))?no_esc}
</span>
</#if>
</div>
</div>
</#if>
<@registerCommons.termsAcceptance/>
<#if recaptchaRequired??>
<div class="form-group">
<div class="${properties.kcInputWrapperClass!}">
<div class="g-recaptcha" data-size="compact" data-sitekey="${recaptchaSiteKey}"></div>
</div>
</div>
</#if>
<div class="${properties.kcFormGroupClass!}">
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doRegister")}"/>
</div>
</div>
</form>
</#if>
</@layout.registrationLayout>

View file

@ -0,0 +1,896 @@
/* DIDI Login Theme - Dark Purple */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Outfit:wght@400;500;600;700;800&display=swap');
/* ========== ROOT VARIABLES ========== */
:root {
/* Background */
--bg-primary: #050510;
--bg-secondary: #0a0a1a;
--surface: rgba(255, 255, 255, 0.03);
--surface-hover: rgba(255, 255, 255, 0.06);
/* Borders */
--border: rgba(255, 255, 255, 0.08);
--border-hover: rgba(255, 255, 255, 0.15);
--border-focus: rgba(139, 92, 246, 0.5);
/* Text */
--text-primary: #ffffff;
--text-secondary: rgba(255, 255, 255, 0.6);
--text-muted: rgba(255, 255, 255, 0.4);
/* Purple accent */
--purple-light: #A855F7;
--purple-main: #8B5CF6;
--purple-dark: #7C3AED;
--purple-deeper: #6D28D9;
--gradient-cta: linear-gradient(135deg, #A855F7 0%, #7C3AED 50%, #6D28D9 100%);
--glow: linear-gradient(135deg, #8B5CF6, #7C3AED);
/* Semantic */
--warning: #f59e0b;
--success: #10b981;
--error: #E63946;
/* Typography */
--font-display: 'Outfit', 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
--font-body: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
/* Spacing */
--radius-card: 20px;
--radius-normal: 16px;
--radius-button: 12px;
--radius-tag: 8px;
}
/* ========== GLOBAL RESET ========== */
* {
box-sizing: border-box;
}
html, body {
margin: 0;
padding: 0;
min-height: 100vh;
}
body {
font-family: var(--font-body);
font-weight: 400;
background: var(--bg-primary);
color: var(--text-primary);
line-height: 1.6;
}
/* ========== LOGIN PAGE LAYOUT ========== */
html.login-pf,
.login-pf body {
background: var(--bg-primary) !important;
min-height: 100vh !important;
min-height: 100dvh !important; /* Dynamic viewport height for mobile */
margin: 0 !important;
padding: 0 !important;
}
#kc-header {
display: none;
}
#kc-header-wrapper {
display: none;
}
/* Force perfect centering */
.login-pf .container-fluid,
.login-pf .container {
background: var(--bg-primary) !important;
min-height: 100vh !important;
min-height: 100dvh !important;
width: 100% !important;
max-width: 100% !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
padding: 20px !important;
margin: 0 !important;
position: relative !important;
}
/* Override any row/column that might break centering */
.login-pf .row,
.login-pf .col-sm-12,
.login-pf .col-md-12,
.login-pf [class*="col-"] {
display: flex !important;
align-items: center !important;
justify-content: center !important;
width: 100% !important;
max-width: 100% !important;
margin: 0 !important;
padding: 0 !important;
float: none !important;
}
/* ========== LOGIN CARD ========== */
#kc-form-wrapper,
#kc-content,
#kc-content-wrapper {
background: transparent !important;
border: none !important;
box-shadow: none !important;
width: 100% !important;
}
.card-pf {
display: flex !important;
flex-direction: column !important;
align-items: center !important;
background: transparent !important;
border: none !important;
box-shadow: none !important;
max-width: 420px;
width: 100%;
margin: 0 auto !important;
padding: 20px;
flex-shrink: 0;
/* Prevent overflow on small screens */
overflow: hidden;
}
#kc-form,
#kc-register-form,
#kc-reset-password-form {
background: var(--surface);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid var(--border);
border-radius: var(--radius-card);
padding: 48px 40px;
max-width: 420px;
width: 100%;
margin: 0 auto;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
/* ========== LOGO / HEADER - Inside card ========== */
.card-pf::before {
content: 'didi';
display: block;
text-align: center;
font-family: var(--font-display);
font-size: 48px;
font-weight: 800;
letter-spacing: -0.04em;
color: #ffffff;
margin-bottom: 4px;
order: -2;
}
.card-pf::after {
content: 'Misinformation Detection Platform';
display: block;
text-align: center;
font-family: var(--font-body);
font-size: 14px;
color: var(--text-secondary);
margin-bottom: 24px;
letter-spacing: 0.5px;
order: -1;
}
/* Hide header outside modal */
#kc-form-wrapper::before,
#kc-form-wrapper::after,
.login-pf-header,
#kc-header,
#kc-header-wrapper {
display: none !important;
}
/* ========== FORM TITLES ========== */
#kc-page-title,
.kc-page-title,
h1#kc-page-title {
font-family: var(--font-display);
font-size: 24px;
font-weight: 700;
color: var(--text-primary);
text-align: center;
margin: 0 0 24px 0;
letter-spacing: -0.02em;
line-height: 1.2;
}
/* ========== FORM GROUPS ========== */
.form-group {
margin-bottom: 20px;
}
/* ========== LABELS ========== */
label,
.control-label,
#kc-form-options label {
display: block;
font-family: var(--font-body);
font-size: 14px;
font-weight: 500;
color: var(--text-secondary);
margin-bottom: 8px;
}
/* ========== INPUT FIELDS ========== */
input[type="text"],
input[type="password"],
input[type="email"],
input[type="tel"],
.form-control {
width: 100%;
padding: 14px 16px;
font-family: var(--font-body);
font-size: 15px;
color: var(--text-primary);
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-button);
outline: none;
transition: all 0.2s ease;
}
input[type="text"]:hover,
input[type="password"]:hover,
input[type="email"]:hover,
input[type="tel"]:hover,
.form-control:hover {
border-color: var(--border-hover);
background: var(--surface-hover);
}
input[type="text"]:focus,
input[type="password"]:focus,
input[type="email"]:focus,
input[type="tel"]:focus,
.form-control:focus {
border-color: var(--border-focus);
background: var(--surface);
box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.15);
}
input::placeholder,
.form-control::placeholder {
color: var(--text-muted);
}
/* ========== PRIMARY BUTTON ========== */
input[type="submit"],
button[type="submit"],
.btn-primary,
#kc-login,
#kc-form-buttons input[type="submit"] {
width: 100%;
padding: 16px 32px;
font-family: var(--font-body);
font-size: 16px;
font-weight: 600;
color: #ffffff;
background: var(--gradient-cta);
border: none;
border-radius: var(--radius-button);
cursor: pointer;
transition: all 0.2s ease;
margin-top: 8px;
}
input[type="submit"]:hover,
button[type="submit"]:hover,
.btn-primary:hover,
#kc-login:hover {
transform: scale(1.02);
box-shadow: 0 0 30px rgba(139, 92, 246, 0.4);
}
input[type="submit"]:active,
button[type="submit"]:active,
.btn-primary:active {
transform: scale(0.98);
}
/* Spacing between Submit and Cancel buttons */
#kc-form-buttons {
display: flex;
flex-direction: column;
gap: 12px;
}
/* ========== SECONDARY / LINK BUTTONS ========== */
.btn-default,
a.btn {
display: inline-block;
padding: 12px 24px;
font-family: var(--font-body);
font-size: 14px;
font-weight: 500;
color: var(--text-secondary);
background: transparent;
border: 1px solid var(--border-hover);
border-radius: var(--radius-button);
text-decoration: none;
cursor: pointer;
transition: all 0.2s ease;
}
.btn-default:hover,
a.btn:hover {
color: var(--text-primary);
border-color: rgba(255, 255, 255, 0.3);
background: var(--surface-hover);
}
/* ========== LINKS ========== */
a,
#kc-registration a,
#kc-form-options a,
.kc-form-options a {
color: var(--purple-light);
text-decoration: none;
font-weight: 500;
transition: color 0.2s ease;
}
a:hover {
color: var(--purple-main);
text-decoration: underline;
}
/* ========== FORM OPTIONS (Remember me, Forgot password) ========== */
/* Both #kc-form-options and the Forgot Password div are children of .login-pf-settings.
We make the parent a flex row so they sit side by side. */
.login-pf-settings {
display: flex !important;
flex-direction: row !important;
flex-wrap: nowrap !important;
align-items: center !important;
justify-content: space-between !important;
}
#kc-form-options {
flex: 0 0 auto;
}
/* Forgot Password - the anonymous div right after #kc-form-options */
#kc-form-options + div {
flex: 0 0 auto;
text-align: right;
}
/* Back link at top of reset password form */
.back-link {
margin-bottom: 24px;
text-align: center;
}
.back-link a {
color: var(--purple-light);
font-size: 14px;
text-decoration: none;
}
.back-link a:hover {
text-decoration: underline;
}
/* Mobile responsive */
@media (max-width: 480px) {
.back-link {
text-align: center;
margin-bottom: 20px;
}
}
#kc-form-options span,
#kc-form-options .checkbox {
font-size: 14px;
color: var(--text-secondary);
}
/* ========== CHECKBOX ========== */
input[type="checkbox"] {
width: 15px;
height: 15px;
accent-color: var(--purple-main);
cursor: pointer;
}
/* Fix Remember-me alignment: cancel inherited block label + Patternfly padding,
make label a flex row so the box and the text sit on the same baseline.
Patternfly default uses `position: absolute` on the checkbox + `padding-left`
on the label both must be reset for flex layout to work. */
#kc-form-options .checkbox {
margin: 0 !important;
padding: 0 !important;
position: static !important;
}
#kc-form-options .checkbox label {
display: flex !important;
align-items: center;
gap: 8px;
margin: 0 !important;
padding: 0 !important;
cursor: pointer;
font-size: 14px;
line-height: 1;
color: var(--text-secondary);
font-weight: 500;
position: static !important;
}
#kc-form-options .checkbox input[type="checkbox"] {
position: static !important; /* override Patternfly position: absolute */
margin: 0 !important; /* override Patternfly negative margin-left */
flex-shrink: 0;
float: none !important; /* in case Patternfly floats it */
vertical-align: middle;
}
/* ========== REGISTRATION LINK ========== */
#kc-registration {
text-align: center;
margin-top: 24px;
padding-top: 24px;
border-top: 1px solid var(--border);
}
#kc-registration span {
color: var(--text-secondary);
font-size: 14px;
}
/* ========== INFO / MESSAGES ========== */
#kc-info,
.kc-feedback-text,
#kc-info-wrapper {
text-align: center;
margin-top: 20px;
}
#kc-info-message,
.kc-feedback-text {
font-size: 14px;
color: var(--text-secondary);
line-height: 1.6;
}
/* ========== ALERTS ========== */
.alert {
padding: 14px 18px;
border-radius: var(--radius-button);
margin-bottom: 20px;
font-size: 14px;
line-height: 1.5;
}
.alert-error,
.alert-danger {
background: rgba(230, 57, 70, 0.1);
border: 1px solid rgba(230, 57, 70, 0.3);
color: var(--error);
}
.alert-warning {
background: rgba(245, 158, 11, 0.1);
border: 1px solid rgba(245, 158, 11, 0.3);
color: var(--warning);
}
.alert-success {
background: rgba(16, 185, 129, 0.1);
border: 1px solid rgba(16, 185, 129, 0.3);
color: var(--success);
}
.alert-info {
background: rgba(139, 92, 246, 0.1);
border: 1px solid rgba(139, 92, 246, 0.3);
color: var(--purple-light);
}
/* ========== SOCIAL PROVIDERS ========== */
#kc-social-providers {
margin-top: 24px;
padding-top: 24px;
border-top: 1px solid var(--border);
}
#kc-social-providers h4,
#kc-social-providers .kc-social-title {
text-align: center;
font-size: 14px;
color: var(--text-secondary);
margin-bottom: 16px;
}
#kc-social-providers ul {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 12px;
}
#kc-social-providers li a,
.kc-social-provider-link {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
padding: 14px 20px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-button);
color: var(--text-primary);
text-decoration: none;
font-size: 14px;
font-weight: 500;
transition: all 0.2s ease;
}
#kc-social-providers li a:hover,
.kc-social-provider-link:hover {
background: var(--surface-hover);
border-color: var(--border-hover);
}
/* ========== TERMS / RECAPTCHA ========== */
#kc-terms-text {
font-size: 13px;
color: var(--text-muted);
line-height: 1.6;
text-align: center;
margin-top: 16px;
}
/* ========== PASSWORD VISIBILITY TOGGLE ========== */
.kc-form-password-container {
position: relative;
}
.kc-form-password-container button {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
background: transparent;
border: none;
color: var(--text-muted);
cursor: pointer;
padding: 4px;
}
.kc-form-password-container button:hover {
color: var(--text-secondary);
}
/* ========== RESPONSIVE ========== */
@media (max-width: 480px) {
#kc-form,
#kc-register-form,
#kc-reset-password-form {
padding: 28px 28px;
margin: 0 auto;
border-radius: var(--radius-normal);
max-width: 100%;
}
.card-pf::before {
font-size: 36px;
}
input[type="submit"],
button[type="submit"],
.btn-primary {
padding: 14px 24px;
font-size: 15px;
}
}
/* ========== HIDE UNNECESSARY ELEMENTS ========== */
#kc-locale-wrapper,
.kc-locale-dropdown,
.pf-c-alert__icon,
#kc-header-wrapper {
display: none !important;
}
/* Hide PatternFly error icons (red circles) */
.pf-c-form-control__icon,
.pf-c-form-control__utilities,
.pf-c-form__helper-text,
input[aria-invalid="true"] + .pf-c-form-control__utilities,
.kc-feedback-text::before,
span[class*="error"]::before,
span[class*="Error"]::before {
display: none !important;
}
/* Simple red border for invalid inputs */
input[aria-invalid="true"],
input.pf-m-error,
.pf-c-form-control.pf-m-error {
border-color: var(--error) !important;
background-image: none !important;
background: var(--surface) !important;
}
/* ========== FOOTER ========== */
#kc-content::after {
content: '© 2025 didi';
display: block;
text-align: center;
font-size: 12px;
color: var(--text-muted);
margin-top: 32px;
}
/* ========== BACK TO LOGIN LINK ========== */
#kc-info {
order: 100;
margin-top: 24px !important;
}
#kc-info-wrapper {
text-align: center;
}
/* ========== PLACEHOLDERS ========== */
input#username::placeholder,
input#password::placeholder,
input#email::placeholder,
input#firstName::placeholder,
input#lastName::placeholder,
input#password-new::placeholder,
input#password-confirm::placeholder {
color: var(--text-muted);
opacity: 1;
}
/* Add placeholder text via CSS for inputs that don't have it */
input#username:placeholder-shown::placeholder { content: 'Enter your email'; }
input#password:placeholder-shown::placeholder { content: 'Enter your password'; }
/* ========== FIX FORM LAYOUT ========== */
#kc-form-login,
#kc-register-form,
#kc-reset-password-form {
display: flex !important;
flex-direction: column !important;
}
/* Form groups (inputs) - first */
.form-group {
order: 1 !important;
}
/* Remember me / Forgot password settings row - between inputs and button */
.login-pf-settings {
order: 2 !important;
}
/* Submit button - last */
#kc-form-buttons {
order: 3 !important;
}
/* ========== KC-INFO SECTION ========== */
#kc-info {
text-align: center !important;
margin-top: 20px !important;
}
#kc-info-wrapper {
text-align: center !important;
}
#kc-info a {
color: var(--purple-light) !important;
font-weight: 500 !important;
}
/* ========== EMAIL VERIFICATION PAGE ========== */
#kc-content,
#kc-content-wrapper {
text-align: center !important;
}
/* Purple box for main instruction */
.verify-email-box {
background: rgba(139, 92, 246, 0.1) !important;
border: 1px solid rgba(139, 92, 246, 0.3) !important;
border-radius: 12px !important;
padding: 28px !important;
margin: 0 0 24px 0 !important;
text-align: center !important;
}
.verify-email-box .instruction {
text-align: center !important;
margin: 0 !important;
color: var(--text-primary) !important;
}
.verify-email-box .instruction.auto-check {
margin-top: 20px !important;
font-size: 13px !important;
color: var(--text-muted) !important;
}
.instruction {
text-align: center !important;
font-size: 15px;
color: var(--text-secondary);
line-height: 1.6;
margin: 0 0 16px 0;
}
/* Haven't received section - also in purple box */
#kc-info .instruction {
margin: 0 !important;
text-align: center !important;
}
/* Loader animation for auto-check */
.loader {
display: inline-block;
width: 14px;
height: 14px;
border: 2px solid rgba(139, 92, 246, 0.3);
border-top-color: var(--purple-main);
border-radius: 50%;
animation: spin 1s linear infinite;
margin-left: 8px;
vertical-align: middle;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* ========== FALLBACK CENTERING (for older browsers) ========== */
@supports not (min-height: 100dvh) {
.login-pf .container-fluid,
.login-pf .container {
min-height: 100vh !important;
min-height: calc(var(--vh, 1vh) * 100) !important;
}
}
/* ========== MOBILE SPECIFIC FIXES ========== */
@media screen and (max-width: 768px) {
.login-pf .container-fluid,
.login-pf .container {
padding: 12px !important;
min-height: 100vh !important;
min-height: 100dvh !important;
}
.card-pf {
max-width: 100%;
padding: 8px;
margin: 0 auto !important;
}
.card-pf::before {
font-size: 40px;
margin-bottom: 4px;
}
.card-pf::after {
font-size: 13px;
margin-bottom: 20px;
}
/* Prevent horizontal overflow */
#kc-form,
#kc-register-form {
max-width: calc(100vw - 40px);
}
}
/* ========== iOS SAFARI SPECIFIC ========== */
@supports (-webkit-touch-callout: none) {
.login-pf .container-fluid,
.login-pf .container {
min-height: -webkit-fill-available !important;
}
/* Fix iOS input zoom */
input[type="text"],
input[type="password"],
input[type="email"],
.form-control {
font-size: 16px !important;
}
}
/* ========== PatternFly v4 input-group fix (Keycloak 26) ========== */
/* Password input wrapped in .pf-c-input-group for eye-icon toggle.
PF4 specificity overrides our base input rules force theme. */
.pf-c-input-group {
background: transparent !important;
border-radius: var(--radius-button) !important;
overflow: hidden;
border: 1px solid var(--border) !important;
}
.pf-c-input-group:hover {
border-color: var(--border-hover) !important;
}
.pf-c-input-group:focus-within {
border-color: var(--border-focus) !important;
box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.15) !important;
}
.pf-c-input-group .pf-c-form-control,
.pf-c-input-group input[type="password"],
.pf-c-input-group input[type="text"],
.pf-c-input-group input[type="email"] {
background: var(--surface) !important;
color: var(--text-primary) !important;
border: none !important;
border-radius: 0 !important;
box-shadow: none !important;
}
.pf-c-input-group .pf-c-button.pf-m-control {
background: var(--surface) !important;
color: var(--text-primary) !important;
border: none !important;
padding: 0 16px !important;
}
.pf-c-input-group .pf-c-button.pf-m-control:hover {
background: var(--surface-hover) !important;
color: var(--accent) !important;
}
.pf-c-input-group .pf-c-button.pf-m-control .fa {
color: var(--text-muted);
}
/* Eye-icon button: kill all PF4 borders/shadows */
.pf-c-input-group .pf-c-button,
.pf-c-input-group .pf-c-button.pf-m-control {
border: 0 !important;
border-left: 1px solid rgba(255, 255, 255, 0.04) !important;
outline: 0 !important;
box-shadow: none !important;
border-radius: 0 !important;
}
.pf-c-input-group .pf-c-button:focus,
.pf-c-input-group .pf-c-button:hover {
outline: 0 !important;
box-shadow: none !important;
}
/* Force ALL borders off on eye button (override previous border-left) */
.pf-c-input-group > .pf-c-button,
.pf-c-input-group > .pf-c-button.pf-m-control {
border: 0 !important;
border-left: 0 !important;
border-right: 0 !important;
border-top: 0 !important;
border-bottom: 0 !important;
outline: 0 !important;
box-shadow: none !important;
border-radius: 0 !important;
background: var(--surface) !important;
--pf-c-button--BorderColor: transparent !important;
--pf-c-button--BorderWidth: 0 !important;
--pf-c-button--m-control--BorderBottomColor: transparent !important;
}
/* THE actual chenar comes from ::after pseudo-element (PF4 pattern) */
.pf-c-input-group .pf-c-button::after,
.pf-c-input-group .pf-c-button.pf-m-control::after,
.pf-c-input-group .pf-c-button:after,
.pf-c-input-group .pf-c-button.pf-m-control:after {
display: none !important;
border: 0 !important;
content: none !important;
}

View file

@ -0,0 +1,28 @@
// Add placeholders to Keycloak forms
document.addEventListener('DOMContentLoaded', function() {
// Common fields
var username = document.getElementById('username');
if (username) username.placeholder = 'Enter your email';
var email = document.getElementById('email');
if (email) email.placeholder = 'Enter your email';
// Detect register page by presence of password-confirm
var passwordConfirm = document.getElementById('password-confirm');
var password = document.getElementById('password');
if (password) {
password.placeholder = passwordConfirm ? 'Create a password' : 'Enter your password';
}
if (passwordConfirm) {
passwordConfirm.placeholder = 'Confirm password';
}
// Register-only fields
var firstName = document.getElementById('firstName');
if (firstName) firstName.placeholder = 'First name';
var lastName = document.getElementById('lastName');
if (lastName) lastName.placeholder = 'Last name';
});

View file

@ -0,0 +1,12 @@
# Inherit from keycloak to get templates
parent=keycloak
# Override styles
styles=css/login.css
# Scripts
scripts=js/placeholders.js
# Messages
displayName=DIDI - Misinformation Detection Platform
displayNameHtml=<strong>DIDI</strong> - Detect. Investigate. Decide. Inform.