feat(auth-ui): redesign admin & reseller login as sci-fi HUD

Replace the admin and reseller login pages with a single-look, dark
"Core Access" HUD console: a full-bleed cyberpunk backdrop with a
centred angular panel, red LED-style edge bars (glowing 45° corner
brackets + neutral edge runs), textured inputs with icon wells and
red accent bars, a chevron LOGIN button, and a subtle living animation
(phase-staggered glow breathing + a slow hue-drift shimmer), gated
behind prefers-reduced-motion.

- admin/login.php: rewritten to the HUD markup; PHP login contract
  preserved 1:1 (flood check, Authenticator, referrer/setup/dashboard
  redirects, reCAPTCHA, status messages). Intentionally not theme/hue
  aware.
- reseller/login.php: same HUD, adapted contract (STATUS_NOT_RESELLER,
  controller-provided $referrer). Reuses the admin login.css and bg via
  the Public/assets/reseller -> admin symlink.
- CSS extracted to assets/admin/new/xcvm/login.css (shared by both).
- Add xcvm-login-bg.png; drop the old login-bg.mp4 video.
- i18n: add admin_access / reseller_access.
This commit is contained in:
Divarion_D
2026-09-06 17:21:31 +03:00
parent c4208902f9
commit abaa7b0b6b
6 changed files with 1114 additions and 426 deletions
+2
View File
@@ -2006,3 +2006,5 @@ xmltv_epg = "XMLTV EPG"
custom_map_info = "Custom maps can only be applied to single-source streams. If you have more than one and the active source changes, a custom map could prevent that source from working."
rtmp_push_info = "RTMP Push lets you push your streams to RTMP servers, such as the one bundled with XC_VM. The Push From server must be enabled in the Servers tab for this to activate."
epg_picon_prompt = "Use the EPG icon for this stream?"
admin_access = "Admin Access"
reseller_access = "Reseller Access"
+149 -320
View File
@@ -73,349 +73,179 @@ if (!isset($_SESSION['hash'])) {
exit();
}
}
// Bootstrap 5 (new-UI) login — XC_VM "Core Access" HUD: a full-bleed sci-fi
// backdrop with a single angular, red-accented sign-in console in the centre.
// Deliberately single-look (dark + red), so it does not follow the theme/hue
// cookies the rest of the panel uses.
$rBrand = $rSettings['server_name'] ?: 'XC_VM';
$rStatusMessages = [
STATUS_FAILURE => 'login_message_1',
STATUS_INVALID_CODE => 'login_message_2',
STATUS_NOT_ADMIN => 'login_message_3',
STATUS_DISABLED => 'login_message_4',
STATUS_INVALID_CAPTCHA => 'login_message_5',
];
$rYears = (date('Y') === '2025') ? '2025' : '2025' . date('Y');
$rRecaptcha = (bool)($rSettings['recaptcha_enable'] ?? false);
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title data-id="login">XC_VM | <?= $language::get('login') ?></title>
<meta charset="UTF-8">
<title data-id="login"><?= htmlspecialchars($rBrand, ENT_QUOTES) ?> | <?= $language::get('login') ?></title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="shortcut icon" href="assets/old/images/favicon.ico">
<link href="assets/old/css/icons.css" rel="stylesheet">
<?php if (isset($_COOKIE['theme']) && $_COOKIE['theme'] == 1): ?>
<link href="assets/old/css/bootstrap.dark.css" rel="stylesheet">
<link href="assets/old/css/app.dark.css" rel="stylesheet">
<?php else: ?>
<link href="assets/old/css/bootstrap.css" rel="stylesheet">
<link href="assets/old/css/app.css" rel="stylesheet">
<?php endif; ?>
<meta name="robots" content="noindex,nofollow">
<link rel="icon" type="image/x-icon" href="assets/old/images/favicon.ico">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Chakra+Petch:wght@400;500;600;700&family=Share+Tech+Mono&display=swap">
<link href="assets/old/css/extra.css" rel="stylesheet">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body,
html {
height: 100%;
overflow: hidden;
}
.login-container {
display: flex;
height: 100vh;
width: 100vw;
}
.video-section {
width: 63%;
height: 100%;
position: relative;
overflow: hidden;
}
.background-video {
position: absolute;
top: 45%;
left: 50%;
min-width: 100%;
min-height: 100%;
width: auto;
height: auto;
z-index: 1;
transform: translateX(-50%) translateY(-50%);
background-size: cover;
}
.video-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(135deg, rgba(0, 0, 0, 0.3) 0%, rgba(0, 0, 0, 0.6) 100%);
z-index: 0;
}
.login-section {
width: 37%;
height: 100%;
display: flex;
flex-direction: column;
position: relative;
}
.login-content {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 40px;
background: <?= isset($_COOKIE['theme']) && $_COOKIE['theme'] == 1 ? '#1a1d29' : '#ffffff' ?>;
}
.login-form-wrapper {
width: 100%;
max-width: 565px;
}
.logo-section {
text-align: center;
margin-bottom: 40px;
}
.logo-section img {
height: 80px;
margin-bottom: 20px;
}
.login-title {
font-size: 28px;
font-weight: 600;
margin-bottom: 10px;
color: <?= isset($_COOKIE['theme']) && $_COOKIE['theme'] == 1 ? '#ffffff' : '#2c3e50' ?>;
}
.login-subtitle {
color: <?= isset($_COOKIE['theme']) && $_COOKIE['theme'] == 1 ? '#8b93a7' : '#7c8db0' ?>;
margin-bottom: 30px;
}
.login-form .card {
border: none;
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
border-radius: 15px;
overflow: hidden;
}
.login-form .card-body {
padding: 40px;
background: <?= isset($_COOKIE['theme']) && $_COOKIE['theme'] == 1 ? '#252a3d' : '#ffffff' ?>;
}
.login-form .form-control {
border-radius: 10px;
padding: 15px 20px;
font-size: 14px;
border: 2px solid <?= isset($_COOKIE['theme']) && $_COOKIE['theme'] == 1 ? '#3a4157' : '#e8ecf4' ?>;
background: <?= isset($_COOKIE['theme']) && $_COOKIE['theme'] == 1 ? '#1e2139' : '#f8f9fa' ?>;
transition: all 0.3s ease;
}
.login-form .form-control:focus {
border-color: <?= isset($_COOKIE['hue']) && !empty($_COOKIE['hue']) && isset($rHues[$_COOKIE['hue']]) ? $rHues[$_COOKIE['hue']] : '#4fc3f7' ?>;
box-shadow: 0 0 0 0.2rem <?= isset($_COOKIE['hue']) && !empty($_COOKIE['hue']) && isset($rHues[$_COOKIE['hue']]) ? $rHues[$_COOKIE['hue']] . '40' : '#4fc3f740' ?>;
}
.login-form label {
font-weight: 600;
margin-bottom: 8px;
color: <?= isset($_COOKIE['theme']) && $_COOKIE['theme'] == 1 ? '#ffffff' : '#2c3e50' ?>;
}
.login-btn {
border-radius: 10px;
padding: 15px;
font-size: 16px;
font-weight: 600;
border: none;
width: 100%;
transition: all 0.3s ease;
background: linear-gradient(135deg, <?= isset($_COOKIE['hue']) && !empty($_COOKIE['hue']) && isset($rHues[$_COOKIE['hue']]) ? $rHues[$_COOKIE['hue']] . ', ' . $rHues[$_COOKIE['hue']] . 'cc' : '#4fc3f7, #4fc3f7cc' ?>);
}
.login-btn:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
}
.alert {
border-radius: 10px;
border: none;
padding: 15px 20px;
margin-bottom: 25px;
}
.g-recaptcha {
display: flex;
justify-content: center;
margin: 20px 0;
}
.login-footer {
background: <?= isset($_COOKIE['theme']) && $_COOKIE['theme'] == 1 ? '#252a3d' : '#f8f9fa' ?>;
padding: 20px 40px;
border-top: 1px solid <?= isset($_COOKIE['theme']) && $_COOKIE['theme'] == 1 ? '#3a4157' : '#e8ecf4' ?>;
text-align: center;
color: <?= isset($_COOKIE['theme']) && $_COOKIE['theme'] == 1 ? '#8b93a7' : '#7c8db0' ?>;
font-size: 14px;
}
@media (max-width: 992px) {
.login-container {
flex-direction: column;
}
.video-section {
display: none;
}
.login-section {
width: 100%;
}
.login-content {
padding: 20px;
}
.login-form .card-body {
padding: 30px;
}
}
@media (max-width: 576px) {
.login-content {
padding: 15px;
}
.login-form .card-body {
padding: 20px;
}
.login-footer {
padding: 15px 20px;
}
}
</style>
<link rel="stylesheet" href="assets/new/xcvm/login.css">
</head>
<body>
<div class="login-container">
<div class="video-section">
<video class="background-video" autoplay muted loop>
<source src="assets/old/videos/login-bg.mp4" type="video/mp4">
<source src="assets/old/videos/login-bg.webm" type="video/webm">
</video>
<div class="video-overlay"></div>
</div>
<div class="login-section">
<div class="login-content">
<div class="login-form-wrapper">
<div class="logo-section">
<img src="assets/old/images/logo.png" alt="XC_VM Logo">
<div class="login-title">Welcome Back</div>
<div class="login-subtitle">Sign in to your account</div>
</div>
<?php if (isset($_STATUS)): ?>
<?php switch ($_STATUS):
case STATUS_FAILURE: ?>
<div class="alert alert-danger alert-dismissible bg-danger text-white border-0 fade show" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<?= $language::get('login_message_1') ?>
</div>
<?php break; ?>
<?php
case STATUS_INVALID_CODE: ?>
<div class="alert alert-danger alert-dismissible bg-danger text-white border-0 fade show" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<?= $language::get('login_message_2') ?>
</div>
<?php break; ?>
<?php
case STATUS_NOT_ADMIN: ?>
<div class="alert alert-danger alert-dismissible bg-danger text-white border-0 fade show" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<?= $language::get('login_message_3') ?>
</div>
<?php break; ?>
<?php
case STATUS_DISABLED: ?>
<div class="alert alert-danger alert-dismissible bg-danger text-white border-0 fade show" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<?= $language::get('login_message_4') ?>
</div>
<?php break; ?>
<?php
case STATUS_INVALID_CAPTCHA: ?>
<div class="alert alert-danger alert-dismissible bg-danger text-white border-0 fade show" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<?= $language::get('login_message_5') ?>
</div>
<?php endswitch; ?>
<?php endif; ?>
<form action="./login" method="POST" data-parsley-validate class="login-form">
<div class="card">
<div class="card-body">
<input type="hidden" name="referrer" value="<?= htmlspecialchars(RequestManager::get('referrer') ?? '') ?>">
<div class="background"></div>
<div class="grid"></div>
<div class="form-group mb-3" id="username_group">
<label for="username"><?= $language::get('username') ?></label>
<input class="form-control" autocomplete="off" type="text" id="username" name="username" required
data-parsley-trigger="change" placeholder="<?= $language::get('enter_your_username') ?>">
</div>
<div class="page">
<div class="stage">
<section class="login-panel">
<span class="panel-frame" aria-hidden="true"></span>
<div class="form-group mb-3">
<label for="password"><?= $language::get('password') ?></label>
<input class="form-control" autocomplete="off" type="password" required
data-parsley-trigger="change" id="password" name="password"
placeholder="<?= $language::get('enter_your_password') ?>">
</div>
<?php if ($rSettings['recaptcha_enable'] ?? false): ?>
<div class="text-center">
<div class="g-recaptcha" data-callback="recaptchaCallback"
data-expired-callback="recaptchaExpired"
id="verification" data-sitekey="<?= $rSettings['recaptcha_v2_site_key'] ?>"></div>
</div>
<?php endif; ?>
<div class="form-group mb-0 mt-4">
<button class="login-btn" type="submit" id="login_button" name="login"
<?= ($rSettings['recaptcha_enable'] ?? false) ? 'disabled' : '' ?>>
<?= $language::get('login') ?>
</button>
</div>
</div>
</div>
</form>
<div class="panel-logo">
<div class="panel-logo-main"><?= htmlspecialchars($rBrand, ENT_QUOTES) ?></div>
<div class="panel-title"><?= $language::get('admin_access') ?></div>
</div>
</div>
<div class="login-footer">
<?php $rYears = (date('Y') === '2025') ? '2025' : '2025' . date('Y'); ?>
<div>&copy; <?= $rYears ?> <a href="https://github.com/Vateron-Media/XC_VM" target="_blank" rel="noopener noreferrer">Vateron Media</a> &middot; <a href="https://www.gnu.org/licenses/agpl-3.0.html" target="_blank" rel="noopener noreferrer">AGPL-3.0</a></div>
<?php if (isset($_STATUS) && isset($rStatusMessages[$_STATUS])): ?>
<div class="panel-alert" role="alert">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="9" />
<path d="M12 8v5M12 16h.01" />
</svg>
<span><?= $language::get($rStatusMessages[$_STATUS]) ?></span>
</div>
<?php endif; ?>
<form id="loginForm" method="POST" action="./login">
<input type="hidden" name="referrer" value="<?= htmlspecialchars(RequestManager::get('referrer') ?? '', ENT_QUOTES) ?>">
<div class="form-group">
<div class="input-wrapper">
<span class="input-icon user" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="8" r="4" />
<path d="M4 20c0-4 4-6 8-6s8 2 8 6" />
</svg>
</span>
<input type="text" name="username" id="username" autocomplete="username" required autofocus
placeholder="<?= htmlspecialchars($language::get('username'), ENT_QUOTES) ?>">
</div>
<div class="input-wrapper">
<span class="input-icon password" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<rect x="5" y="11" width="14" height="9" rx="2" />
<path d="M8 11V8a4 4 0 0 1 8 0v3" />
</svg>
</span>
<input type="password" name="password" id="password" autocomplete="current-password" required
placeholder="<?= htmlspecialchars($language::get('password'), ENT_QUOTES) ?>">
<button type="button" class="show-password" id="showPassword" aria-label="Show password">
<svg class="eye-on" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<path d="M2 12s3.5-6 10-6 10 6 10 6-3.5 6-10 6S2 12 2 12z" />
<circle cx="12" cy="12" r="3" />
</svg>
<svg class="eye-off" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 3l18 18" />
<path d="M10.6 10.6a3 3 0 0 0 4.2 4.2" />
<path d="M9.4 5.2A10 10 0 0 1 12 5c6.5 0 10 6 10 6a17 17 0 0 1-3.3 3.9M6.1 6.1A17 17 0 0 0 2 12s3.5 6 10 6a10 10 0 0 0 3.3-.5" />
</svg>
</button>
</div>
</div>
<?php if ($rRecaptcha): ?>
<div class="recaptcha-wrap">
<div class="g-recaptcha" id="verification" data-callback="recaptchaCallback" data-expired-callback="recaptchaExpired" data-sitekey="<?= htmlspecialchars($rSettings['recaptcha_v2_site_key'], ENT_QUOTES) ?>"></div>
</div>
<?php endif; ?>
<button class="login-button" type="submit" id="login_button" name="login" <?= $rRecaptcha ? 'disabled' : '' ?>>
<?= $language::get('login') ?>
<span class="arrow">→</span>
</button>
</form>
<span class="panel-divider" aria-hidden="true"></span>
<div class="panel-foot">
&copy; <?= $rYears ?>
<a href="https://github.com/Vateron-Media/XC_VM" target="_blank" rel="noopener noreferrer">Vateron Media</a>
&middot;
<a href="https://www.gnu.org/licenses/agpl-3.0.html" target="_blank" rel="noopener noreferrer">AGPL-3.0</a>
</div>
</section>
<div class="panel-glow" aria-hidden="true">
<span class="g-left"></span>
<span class="g-top"></span>
<span class="g-bl"></span>
<span class="g-blh"></span>
<span class="g-br"></span>
<span class="g-brh"></span>
<span class="g-ticks"><i></i><i></i><i></i><i></i></span>
<span class="g-plus p1"></span>
<span class="g-plus p2"></span>
<span class="g-tl"></span>
<span class="g-tr"></span>
<span class="e e-topl"></span>
<span class="e e-topr"></span>
<span class="e e-lv1"></span>
<span class="e e-rv1"></span>
<span class="e e-rv2"></span>
<span class="e e-botl"></span>
</div>
</div>
</div>
<script src="assets/old/js/vendor.min.js"></script>
<script src="assets/old/libs/parsleyjs/parsley.min.js"></script>
<script src="assets/old/js/app.min.js"></script>
<?php if ($rSettings['recaptcha_enable'] ?? false): ?>
<script>
(function() {
"use strict";
var password = document.getElementById("password");
var showPassword = document.getElementById("showPassword");
if (password && showPassword) {
showPassword.addEventListener("click", function() {
var reveal = password.type === "password";
password.type = reveal ? "text" : "password";
showPassword.classList.toggle("revealed", reveal);
showPassword.setAttribute("aria-label", reveal ? "Hide password" : "Show password");
});
}
})();
</script>
<?php if ($rRecaptcha): ?>
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
<script>
function recaptchaCallback() {
var b = document.getElementById('login_button');
if (b) b.disabled = false;
if (b) {
b.disabled = false;
}
}
function recaptchaExpired() {
var b = document.getElementById('login_button');
if (b) b.disabled = true;
if (b) {
b.disabled = true;
}
}
</script>
<?php endif; ?>
@@ -428,4 +258,3 @@ if (!isset($_SESSION['hash'])) {
exit();
}
?>
+153 -106
View File
@@ -1,120 +1,167 @@
<?php
use XcVm\Core\Reference\UiReference;
$rHues = UiReference::hues();
// Bootstrap 5 (new-UI) reseller login — shares the XC_VM "Core Access" HUD design
// with the admin login (single-look dark + red). Auth/flood/redirect handling is
// done by the reseller controller before this view is included; here we only
// render. Assets resolve to the admin tree via the Public/assets/reseller symlink.
$rBrand = ($rSettings['server_name'] ?? '') ?: 'XC_VM';
$rStatusMessages = [
STATUS_FAILURE => 'login_message_1',
STATUS_INVALID_CODE => 'login_message_2',
STATUS_NOT_RESELLER => 'login_message_3',
STATUS_DISABLED => 'login_message_4',
STATUS_INVALID_CAPTCHA => 'login_message_5',
];
$rYears = (date('Y') === '2025') ? '2025' : '2025' . date('Y');
$rRecaptcha = (bool)($rSettings['recaptcha_enable'] ?? false);
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title data-id="login">XC_VM | <?= $language::get('login') ?></title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<link rel="shortcut icon" href="assets/old/images/favicon.ico">
<link href="assets/old/css/icons.css" rel="stylesheet" type="text/css" />
<?php if (isset($_COOKIE['theme']) && $_COOKIE['theme'] == 1): ?>
<link href="assets/old/css/bootstrap.dark.css" rel="stylesheet" type="text/css" />
<link href="assets/old/css/app.dark.css" rel="stylesheet" type="text/css" />
<?php else: ?>
<link href="assets/old/css/bootstrap.css" rel="stylesheet" type="text/css" />
<link href="assets/old/css/app.css" rel="stylesheet" type="text/css" />
<?php endif; ?>
<link href="assets/old/css/extra.css" rel="stylesheet" type="text/css" />
<style>
.g-recaptcha {
display: inline-block;
}
.vertical-center {
margin: 0;
position: absolute;
top: 50%;
-ms-transform: translateY(-50%);
transform: translateY(-50%);
width: 100%;
}
</style>
</head>
<body class="bg-animate<?php if (isset($_COOKIE['hue']) && strlen($_COOKIE['hue']) > 0 && in_array($_COOKIE['hue'], array_keys($rHues))): ?>-<?= $_COOKIE['hue'] ?><?php endif; ?>">
<div class="body-full navbar-custom">
<div class="account-pages vertical-center">
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8 col-lg-6 col-xl-5">
<div class="text-center w-75 m-auto">
<span><img src="assets/old/images/logo.png" height="80px" alt=""></span>
<p class="text-muted mb-4 mt-3"></p>
</div>
<?php if (isset($_STATUS) && $_STATUS == STATUS_FAILURE): ?>
<div class="alert alert-danger alert-dismissible bg-danger text-white border-0 fade show" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<?= $language::get('login_message_1') ?>
</div>
<?php elseif (isset($_STATUS) && $_STATUS == STATUS_INVALID_CODE): ?>
<div class="alert alert-danger alert-dismissible bg-danger text-white border-0 fade show" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<?= $language::get('login_message_2') ?>
</div>
<?php elseif (isset($_STATUS) && $_STATUS == STATUS_NOT_RESELLER): ?>
<div class="alert alert-danger alert-dismissible bg-danger text-white border-0 fade show" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<?= $language::get('login_message_3') ?>
</div>
<?php elseif (isset($_STATUS) && $_STATUS == STATUS_DISABLED): ?>
<div class="alert alert-danger alert-dismissible bg-danger text-white border-0 fade show" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<?= $language::get('login_message_4') ?>
</div>
<?php elseif (isset($_STATUS) && $_STATUS == STATUS_INVALID_CAPTCHA): ?>
<div class="alert alert-danger alert-dismissible bg-danger text-white border-0 fade show" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<?= $language::get('login_message_5') ?>
</div>
<?php endif; ?>
<form action="./login" method="POST" data-parsley-validate="">
<div class="card">
<div class="card-body p-4">
<input type="hidden" name="referrer" value="<?= $referrer ?>" />
<div class="form-group mb-3" id="username_group">
<label for="username"><?= $language::get('username') ?></label>
<input class="form-control" autocomplete="off" type="text" id="username" name="username" required data-parsley-trigger="change" placeholder="">
</div>
<div class="form-group mb-3">
<label for="password"><?= $language::get('password') ?></label>
<input class="form-control" autocomplete="off" type="password" required data-parsley-trigger="change" id="password" name="password" placeholder="">
</div>
<?php if ($rSettings['recaptcha_enable']): ?>
<h5 class="auth-title text-center" style="margin-bottom:0;">
<div class="g-recaptcha" data-callback="recaptchaCallback" data-expired-callback="recaptchaExpired" id="verification" data-sitekey="<?= $rSettings['recaptcha_v2_site_key'] ?>"></div>
</h5>
<?php endif; ?>
</div>
</div>
<div class="form-group mb-0 text-center">
<button style="border:0" class="btn btn-info <?php if (isset($_COOKIE['hue']) && strlen($_COOKIE['hue']) > 0 && in_array($_COOKIE['hue'], array_keys($rHues))): ?>bg-animate-<?= $_COOKIE['hue'] ?><?php else: ?>bg-animate-info<?php endif; ?> btn-block" type="submit" id="login_button" name="login"<?php if ($rSettings['recaptcha_enable']): ?> disabled<?php endif; ?>><?= $language::get('login') ?></button>
</div>
</form>
<head>
<meta charset="UTF-8">
<title data-id="login"><?= htmlspecialchars($rBrand, ENT_QUOTES) ?> | <?= $language::get('login') ?></title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="robots" content="noindex,nofollow">
<link rel="icon" type="image/x-icon" href="assets/old/images/favicon.ico">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Chakra+Petch:wght@400;500;600;700&family=Share+Tech+Mono&display=swap">
<link rel="stylesheet" href="assets/new/xcvm/login.css">
</head>
<body>
<div class="background"></div>
<div class="grid"></div>
<div class="page">
<div class="stage">
<section class="login-panel">
<span class="panel-frame" aria-hidden="true"></span>
<div class="panel-logo">
<div class="panel-logo-main"><?= htmlspecialchars($rBrand, ENT_QUOTES) ?></div>
<div class="panel-title"><?= $language::get('reseller_access') ?></div>
</div>
<?php if (isset($_STATUS) && isset($rStatusMessages[$_STATUS])): ?>
<div class="panel-alert" role="alert">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="9" />
<path d="M12 8v5M12 16h.01" />
</svg>
<span><?= $language::get($rStatusMessages[$_STATUS]) ?></span>
</div>
<?php endif; ?>
<form id="loginForm" method="POST" action="./login">
<input type="hidden" name="referrer" value="<?= $referrer ?? '' ?>">
<div class="form-group">
<div class="input-wrapper">
<span class="input-icon user" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="8" r="4" />
<path d="M4 20c0-4 4-6 8-6s8 2 8 6" />
</svg>
</span>
<input type="text" name="username" id="username" autocomplete="username" required autofocus
placeholder="<?= htmlspecialchars($language::get('username'), ENT_QUOTES) ?>">
</div>
<div class="input-wrapper">
<span class="input-icon password" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<rect x="5" y="11" width="14" height="9" rx="2" />
<path d="M8 11V8a4 4 0 0 1 8 0v3" />
</svg>
</span>
<input type="password" name="password" id="password" autocomplete="current-password" required
placeholder="<?= htmlspecialchars($language::get('password'), ENT_QUOTES) ?>">
<button type="button" class="show-password" id="showPassword" aria-label="Show password">
<svg class="eye-on" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<path d="M2 12s3.5-6 10-6 10 6 10 6-3.5 6-10 6S2 12 2 12z" />
<circle cx="12" cy="12" r="3" />
</svg>
<svg class="eye-off" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 3l18 18" />
<path d="M10.6 10.6a3 3 0 0 0 4.2 4.2" />
<path d="M9.4 5.2A10 10 0 0 1 12 5c6.5 0 10 6 10 6a17 17 0 0 1-3.3 3.9M6.1 6.1A17 17 0 0 0 2 12s3.5 6 10 6a10 10 0 0 0 3.3-.5" />
</svg>
</button>
</div>
</div>
<?php if ($rRecaptcha): ?>
<div class="recaptcha-wrap">
<div class="g-recaptcha" id="verification" data-callback="recaptchaCallback" data-expired-callback="recaptchaExpired" data-sitekey="<?= htmlspecialchars($rSettings['recaptcha_v2_site_key'], ENT_QUOTES) ?>"></div>
</div>
<?php endif; ?>
<button class="login-button" type="submit" id="login_button" name="login" <?= $rRecaptcha ? 'disabled' : '' ?>>
<?= $language::get('login') ?>
<span class="arrow">→</span>
</button>
</form>
<span class="panel-divider" aria-hidden="true"></span>
<div class="panel-foot">
&copy; <?= $rYears ?>
<a href="https://github.com/Vateron-Media/XC_VM" target="_blank" rel="noopener noreferrer">Vateron Media</a>
&middot;
<a href="https://www.gnu.org/licenses/agpl-3.0.html" target="_blank" rel="noopener noreferrer">AGPL-3.0</a>
</div>
</section>
<div class="panel-glow" aria-hidden="true">
<span class="g-left"></span>
<span class="g-top"></span>
<span class="g-bl"></span>
<span class="g-blh"></span>
<span class="g-br"></span>
<span class="g-brh"></span>
<span class="g-tl"></span>
<span class="g-tr"></span>
<span class="e e-topl"></span>
<span class="e e-topr"></span>
<span class="e e-lv1"></span>
<span class="e e-rv1"></span>
<span class="e e-rv2"></span>
<span class="e e-botl"></span>
<span class="g-ticks"><i></i><i></i><i></i><i></i></span>
<span class="g-plus p1"></span>
<span class="g-plus p2"></span>
</div>
</div>
<script src="assets/old/js/vendor.min.js"></script>
<script src="assets/old/libs/parsleyjs/parsley.min.js"></script>
<script src="assets/old/js/app.min.js"></script>
<?php if ($rSettings['recaptcha_enable'] ?? false): ?>
</div>
<script>
(function () {
"use strict";
var password = document.getElementById("password");
var showPassword = document.getElementById("showPassword");
if (password && showPassword) {
showPassword.addEventListener("click", function () {
var reveal = password.type === "password";
password.type = reveal ? "text" : "password";
showPassword.classList.toggle("revealed", reveal);
showPassword.setAttribute("aria-label", reveal ? "Hide password" : "Show password");
});
}
})();
</script>
<?php if ($rRecaptcha): ?>
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
<script>
function recaptchaCallback() {
var b = document.getElementById('login_button');
if (b) b.disabled = false;
}
function recaptchaExpired() {
var b = document.getElementById('login_button');
if (b) b.disabled = true;
}
function recaptchaCallback() { var b = document.getElementById('login_button'); if (b) { b.disabled = false; } }
function recaptchaExpired() { var b = document.getElementById('login_button'); if (b) { b.disabled = true; } }
</script>
<?php endif; ?>
</body>
<?php endif; ?>
</body>
</html>
+810
View File
@@ -0,0 +1,810 @@
/* XC_VM admin login — HUD Core Access styles. Loaded by Views/admin/login.php. */
:root {
--red: #ff1744;
--red-soft: #ff4d63;
--red-deep: #90001d;
--red-glow: rgba(255, 23, 68, 0.55);
--text: #edf1f7;
--muted: #8a95a6;
--dim: #6a7484;
--line: rgba(164, 180, 205, 0.16);
--font: "Chakra Petch", "Segoe UI", Arial, sans-serif;
--font-mono: "Share Tech Mono", ui-monospace, "SFMono-Regular", Menlo, monospace;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html,
body {
width: 100%;
min-height: 100%;
font-family: var(--font);
background: #05070b;
color: var(--text);
}
body {
min-height: 100vh;
overflow: hidden;
}
/* ===== BACKGROUND ===== */
.background {
position: fixed;
inset: 0;
z-index: 0;
background:
linear-gradient(180deg, rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.32)),
url("../../old/images/xcvm-login-bg.png") center center / cover no-repeat;
}
.background::after {
content: "";
position: absolute;
inset: 0;
background: radial-gradient(
ellipse at center,
transparent 35%,
rgba(0, 0, 0, 0.18) 70%,
rgba(0, 0, 0, 0.5) 100%
);
}
.grid {
position: fixed;
inset: 0;
z-index: 1;
pointer-events: none;
opacity: 0.045;
background-image:
linear-gradient(rgba(255, 255, 255, 0.2) 1px, transparent 1px),
linear-gradient(90deg, rgba(255, 255, 255, 0.2) 1px, transparent 1px);
background-size: 80px 80px;
}
/* ===== PAGE ===== */
.page {
position: relative;
z-index: 2;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 40px 20px;
}
/* ===== LOGIN PANEL ===== */
.stage {
position: relative;
width: 466px;
}
.login-panel {
position: relative;
width: 100%;
min-height: 560px;
padding: 58px 48px 38px;
background: linear-gradient(
160deg,
rgba(17, 23, 34, 0.94) 0%,
rgba(7, 10, 17, 0.93) 55%,
rgba(10, 14, 22, 0.95) 100%
);
clip-path: polygon(
26px 0,
calc(100% - 26px) 0,
100% 26px,
100% calc(100% - 58px),
calc(100% - 58px) 100%,
58px 100%,
0 calc(100% - 58px),
0 26px
);
box-shadow:
0 45px 110px rgba(0, 0, 0, 0.78),
inset 0 0 0 1px rgba(176, 192, 216, 0.1),
inset 0 1px 0 rgba(255, 255, 255, 0.05);
backdrop-filter: blur(16px);
}
.panel-frame {
position: absolute;
inset: 7px;
pointer-events: none;
border: 1px solid rgba(150, 170, 200, 0.13);
clip-path: polygon(
21px 0,
calc(100% - 21px) 0,
100% 21px,
100% calc(100% - 52px),
calc(100% - 52px) 100%,
52px 100%,
0 calc(100% - 52px),
0 21px
);
}
.acc-tl,
.acc-tr {
position: absolute;
top: 17px;
width: 22px;
height: 22px;
border: 1px solid rgba(164, 180, 205, 0.3);
pointer-events: none;
}
.acc-tl {
left: 17px;
border-right: 0;
border-bottom: 0;
}
.acc-tr {
right: 17px;
border-left: 0;
border-bottom: 0;
}
/* ===== Glow overlay (not clipped, bloom bleeds freely) ===== */
.panel-glow {
position: absolute;
inset: 0;
z-index: 3;
pointer-events: none;
}
.g-left,
.g-bl,
.g-blh,
.g-br,
.g-brh {
position: absolute;
border-radius: 3px;
box-shadow:
0 0 5px #ff2d4d,
0 0 16px 1px rgba(255, 40, 70, 0.75),
0 0 36px 7px rgba(255, 23, 68, 0.38),
0 0 64px 14px rgba(255, 23, 68, 0.16);
}
.g-left {
left: -2px;
top: 150px;
width: 5px;
height: 162px;
background: linear-gradient(90deg, #5c0010, #ff4d63 42%, #ffe1e4 50%, #ff4d63 58%, #5c0010);
}
.g-bl {
left: -1px;
bottom: 57px;
width: 82px;
height: 6px;
z-index: 2;
transform-origin: left bottom;
transform: rotate(45deg);
background: linear-gradient(180deg, #5c0010, #ff4d63 42%, #ffe1e4 50%, #ff4d63 58%, #5c0010);
}
.g-blh {
left: 57px;
bottom: -1px;
width: 56px;
height: 5px;
background: linear-gradient(90deg, #ff4d63 0%, #c0102a 55%, #5c0010 100%);
}
.g-br {
right: -1px;
bottom: 57px;
width: 82px;
height: 6px;
z-index: 2;
transform-origin: right bottom;
transform: rotate(-45deg);
background: linear-gradient(180deg, #5c0010, #ff4d63 42%, #ffe1e4 50%, #ff4d63 58%, #5c0010);
}
.g-brh {
right: 57px;
bottom: -1px;
width: 56px;
height: 5px;
background: linear-gradient(270deg, #ff4d63 0%, #c0102a 55%, #5c0010 100%);
}
.g-top {
position: absolute;
top: -3px;
right: 70px;
width: 104px;
height: 26px;
background: linear-gradient(180deg, #ff92a0 0%, #ff2d4d 45%, #a8001e 100%);
clip-path: polygon(12px 0, 100% 0, calc(100% - 12px) 100%, 0 100%);
box-shadow:
0 0 10px #ff2d4d,
0 0 28px 4px rgba(255, 40, 70, 0.7),
0 0 54px 10px rgba(255, 23, 68, 0.32);
}
.g-top::after {
content: "";
position: absolute;
left: 8px;
right: 14px;
top: 3px;
height: 3px;
background: rgba(255, 220, 225, 0.7);
filter: blur(0.5px);
}
.g-ticks {
position: absolute;
left: 11px;
top: 320px;
display: flex;
flex-direction: column;
gap: 5px;
}
.g-ticks i {
width: 10px;
height: 2px;
background: #ff2d4d;
box-shadow: 0 0 6px rgba(255, 40, 70, 0.85);
}
.g-plus {
position: absolute;
width: 11px;
height: 11px;
}
.g-plus::before,
.g-plus::after {
content: "";
position: absolute;
background: #ff2d4d;
box-shadow: 0 0 6px #ff2d4d;
}
.g-plus::before {
left: 50%;
top: 0;
width: 1.5px;
height: 100%;
transform: translateX(-50%);
}
.g-plus::after {
top: 50%;
left: 0;
height: 1.5px;
width: 100%;
transform: translateY(-50%);
}
.g-plus.p1 {
top: 300px;
right: 16px;
}
.g-plus.p2 {
bottom: 128px;
right: 48px;
}
/* ---- More HUD edge bars along the panel border (45° bends) ---- */
/* Neutral straight runs. */
.e {
position: absolute;
background: rgba(156, 174, 202, 0.3);
border-radius: 2px;
pointer-events: none;
}
.e-topl {
top: -1px;
left: 34px;
width: 96px;
height: 4px;
}
.e-topr {
top: -1px;
right: 186px;
width: 64px;
height: 4px;
}
.e-lv1 {
left: -1px;
top: 38px;
width: 4px;
height: 78px;
}
.e-rv1 {
right: -1px;
top: 40px;
width: 4px;
height: 96px;
}
.e-rv2 {
right: -1px;
bottom: 132px;
width: 4px;
height: 76px;
}
.e-botl {
bottom: -1px;
left: 134px;
width: 78px;
height: 4px;
}
/* Glowing 45° diagonal brackets on the top chamfers. */
.g-tl,
.g-tr {
position: absolute;
width: 40px;
height: 5px;
border-radius: 3px;
background: linear-gradient(180deg, #5c0010, #ff4d63 42%, #ffe1e4 50%, #ff4d63 58%, #5c0010);
box-shadow:
0 0 5px #ff2d4d,
0 0 16px 1px rgba(255, 40, 70, 0.75),
0 0 34px 6px rgba(255, 23, 68, 0.3);
}
.g-tl {
left: -1px;
top: 27px;
transform-origin: left center;
transform: rotate(-45deg);
}
.g-tr {
right: -1px;
top: 27px;
transform-origin: right center;
transform: rotate(45deg);
}
/* ===== LOGO ===== */
.panel-logo {
text-align: center;
margin-bottom: 40px;
}
.panel-logo-main {
font-size: 52px;
font-weight: 700;
letter-spacing: 4px;
line-height: 1;
background: linear-gradient(180deg, #ffffff 0%, #d6dce7 42%, #8b93a4 54%, #eef1f6 100%);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
filter: drop-shadow(0 3px 8px rgba(0, 0, 0, 0.5));
word-break: break-word;
}
.panel-title {
margin-top: 16px;
color: var(--muted);
font-family: var(--font-mono);
font-size: 12px;
letter-spacing: 9px;
text-transform: uppercase;
text-align: center;
padding-left: 9px;
}
/* ===== ALERT ===== */
.panel-alert {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 20px;
padding: 11px 14px;
border: 1px solid rgba(255, 23, 68, 0.5);
background: rgba(255, 23, 68, 0.1);
color: #ffd7dd;
font-family: var(--font-mono);
font-size: 12px;
letter-spacing: 0.4px;
clip-path: polygon(8px 0, 100% 0, 100% calc(100% - 8px), calc(100% - 8px) 100%, 0 100%, 0 8px);
}
.panel-alert svg {
width: 18px;
height: 18px;
flex: none;
color: var(--red);
}
/* ===== INPUTS ===== */
.form-group {
display: flex;
flex-direction: column;
gap: 16px;
}
.input-wrapper {
position: relative;
}
.input-wrapper input {
width: 100%;
height: 58px;
padding: 0 52px 0 70px;
outline: none;
border: 1px solid rgba(150, 168, 196, 0.16);
border-top-color: rgba(200, 215, 235, 0.22);
background:
linear-gradient(90deg, rgba(0, 0, 0, 0.36) 0, rgba(0, 0, 0, 0.36) 52px, transparent 52px),
linear-gradient(180deg, rgba(255, 255, 255, 0.05), transparent 40%), rgba(12, 17, 26, 0.82);
color: #eef2f8;
font-family: var(--font);
font-size: 15px;
font-weight: 500;
letter-spacing: 0.5px;
clip-path: polygon(
14px 0,
calc(100% - 14px) 0,
100% 14px,
100% calc(100% - 14px),
calc(100% - 14px) 100%,
14px 100%,
0 calc(100% - 14px),
0 14px
);
transition:
border-color 0.25s ease,
background 0.25s ease;
}
.input-wrapper input::placeholder {
color: #6f7a8c;
}
.input-wrapper input:focus {
border-color: rgba(255, 23, 68, 0.6);
background:
linear-gradient(90deg, rgba(0, 0, 0, 0.36) 0, rgba(0, 0, 0, 0.36) 52px, transparent 52px),
linear-gradient(180deg, rgba(255, 255, 255, 0.06), transparent 40%), rgba(16, 20, 30, 0.9);
}
.input-wrapper::before {
content: "";
position: absolute;
top: 8px;
bottom: 8px;
right: 12px;
width: 40%;
background-image: radial-gradient(rgba(185, 200, 224, 0.2) 1px, transparent 1.5px);
background-size: 6px 6px;
opacity: 0.6;
pointer-events: none;
-webkit-mask-image: linear-gradient(90deg, transparent, #000 60%);
mask-image: linear-gradient(90deg, transparent, #000 60%);
}
.input-wrapper::after {
content: "";
position: absolute;
left: -2px;
top: 50%;
transform: translateY(-50%);
width: 2px;
height: 30px;
border-radius: 2px;
background: linear-gradient(180deg, #ff4d63, #ffe1e4 50%, #ff4d63);
box-shadow:
0 0 6px #ff2d4d,
0 0 16px 1px rgba(255, 40, 70, 0.7),
0 0 30px 4px rgba(255, 23, 68, 0.3);
pointer-events: none;
}
.input-wrapper:focus-within::after {
box-shadow:
0 0 8px #ff2d4d,
0 0 22px 2px rgba(255, 40, 70, 0.9),
0 0 42px 7px rgba(255, 23, 68, 0.42);
}
.input-icon {
position: absolute;
top: 50%;
left: 18px;
transform: translateY(-50%);
width: 19px;
height: 19px;
color: #9ca8ba;
opacity: 0.85;
pointer-events: none;
z-index: 1;
}
.input-icon svg {
width: 100%;
height: 100%;
display: block;
}
.input-icon::after {
content: "";
position: absolute;
top: -5px;
bottom: -5px;
right: -15px;
width: 1px;
background: rgba(255, 255, 255, 0.12);
}
.input-wrapper:focus-within .input-icon {
color: var(--red);
opacity: 1;
}
.show-password {
position: absolute;
top: 50%;
right: 15px;
transform: translateY(-50%);
display: grid;
place-items: center;
width: 30px;
height: 30px;
border: 0;
background: transparent;
color: #8c98aa;
cursor: pointer;
z-index: 1;
transition: color 0.2s ease;
}
.show-password:hover {
color: #eef2f8;
}
.show-password svg {
width: 19px;
height: 19px;
display: block;
}
.show-password .eye-off {
display: none;
}
.show-password.revealed .eye-on {
display: none;
}
.show-password.revealed .eye-off {
display: block;
}
/* ===== reCAPTCHA ===== */
.recaptcha-wrap {
display: flex;
justify-content: center;
margin-top: 20px;
}
/* ===== LOGIN BUTTON ===== */
.login-button {
position: relative;
display: flex;
align-items: center;
justify-content: center;
gap: 16px;
width: 100%;
height: 60px;
margin-top: 26px;
border: 1px solid var(--red);
background: linear-gradient(
90deg,
rgba(255, 23, 68, 0.44) 0%,
rgba(255, 23, 68, 0.16) 55%,
rgba(255, 23, 68, 0.04) 100%
);
color: #f5f6fa;
cursor: pointer;
font-family: var(--font-mono);
font-size: 14px;
letter-spacing: 6px;
text-transform: uppercase;
clip-path: polygon(
16px 0,
calc(100% - 20px) 0,
100% 50%,
calc(100% - 20px) 100%,
0 100%,
0 16px
);
box-shadow:
0 0 30px rgba(255, 23, 68, 0.32),
inset 0 0 20px rgba(255, 23, 68, 0.14),
inset 0 1px 0 rgba(255, 255, 255, 0.18);
overflow: hidden;
transition:
background 0.25s ease,
box-shadow 0.25s ease,
transform 0.15s ease;
}
.login-button:hover:not(:disabled) {
background: linear-gradient(90deg, rgba(255, 23, 68, 0.58), rgba(255, 23, 68, 0.12));
box-shadow:
0 0 42px rgba(255, 23, 68, 0.5),
inset 0 0 26px rgba(255, 23, 68, 0.2);
transform: translateY(-1px);
}
.login-button:active:not(:disabled) {
transform: translateY(0);
}
.login-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.login-button::before {
content: "";
position: absolute;
top: 0;
bottom: 0;
right: 0;
width: 130px;
pointer-events: none;
background: repeating-linear-gradient(
115deg,
rgba(255, 255, 255, 0.14) 0 2px,
transparent 2px 15px
);
-webkit-mask-image: linear-gradient(90deg, transparent, #000);
mask-image: linear-gradient(90deg, transparent, #000);
opacity: 0.6;
}
.login-button .arrow {
font-size: 18px;
color: #fff;
}
/* ===== FOOTER ===== */
.panel-divider {
display: block;
height: 1px;
margin-top: 24px;
background: linear-gradient(90deg, rgba(150, 168, 196, 0.28), transparent 80%);
}
.panel-foot {
margin-top: 32px;
text-align: center;
font-family: var(--font-mono);
font-size: 17px;
letter-spacing: 1px;
color: var(--dim);
}
.panel-foot a {
color: var(--muted);
text-decoration: none;
}
.panel-foot a:hover {
color: var(--red-soft);
}
/* ===== RESPONSIVE ===== */
@media (max-width: 540px) {
body {
overflow: auto;
}
.stage {
width: 100%;
}
.login-panel {
min-height: auto;
padding: 48px 24px 32px;
}
.panel-logo-main {
font-size: 40px;
}
.g-left {
top: 130px;
height: 120px;
}
}
a:focus-visible,
button:focus-visible,
input:focus-visible {
outline: 2px solid var(--red-soft);
outline-offset: 2px;
}
/* ===== Living / shimmering animation ===== */
@media (prefers-reduced-motion: no-preference) {
/* Slow hue drift across the whole glow layer — the frame "переливается". */
@keyframes xcHueShift {
0%,
100% {
filter: hue-rotate(-8deg);
}
50% {
filter: hue-rotate(12deg);
}
}
.panel-glow {
animation: xcHueShift 9s ease-in-out infinite;
}
/* Breathing glow intensity, phase-staggered per bar for an organic feel. */
@keyframes xcBreath {
0%,
100% {
opacity: 0.72;
}
50% {
opacity: 1;
}
}
.g-left {
animation: xcBreath 3.6s ease-in-out infinite;
}
.g-bl {
animation: xcBreath 4.4s ease-in-out infinite 0.2s;
}
.g-br {
animation: xcBreath 4.1s ease-in-out infinite 0.8s;
}
.g-blh {
animation: xcBreath 3.1s ease-in-out infinite 0.5s;
}
.g-brh {
animation: xcBreath 3.3s ease-in-out infinite 1.1s;
}
.g-tl {
animation: xcBreath 3.9s ease-in-out infinite 0.3s;
}
.g-tr {
animation: xcBreath 4.2s ease-in-out infinite 0.9s;
}
.g-ticks i {
animation: xcBreath 2.6s ease-in-out infinite;
}
.g-plus {
animation: xcBreath 3.4s ease-in-out infinite 0.4s;
}
/* Top indicator block — slower status-beacon pulse. */
@keyframes xcBeacon {
0%,
100% {
opacity: 0.85;
}
50% {
opacity: 1;
}
}
.g-top {
animation: xcBeacon 2.8s ease-in-out infinite;
}
/* Field accent bars gently pulse too. */
.input-wrapper::after {
animation: xcBreath 3.7s ease-in-out infinite;
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.