This commit is contained in:
KONE SOREL 2026-03-12 10:52:05 +00:00
parent 4ca7d812f1
commit 334e02408b
2 changed files with 327 additions and 319 deletions

View File

@ -1582,4 +1582,153 @@ select[class*="selectpicker"],
overflow: hidden;
text-overflow: ellipsis;
display: block !important;
}
}
/* ===== WRAPPER & BORDURE ANIMÉE ===== */
.scan-wrapper {
position: relative;
width: 100%;
max-width: 720px;
margin: 40px auto;
}
.scan-wrapper::before {
content: '';
position: absolute;
inset: -3px;
border-radius: 20px;
background: linear-gradient(135deg, #00c9ff, #0055a5, #00e5ff, #0055a5, #00c9ff);
background-size: 300% 300%;
animation: borderGlow 3s linear infinite;
z-index: 0;
}
.scan-wrapper.has-value::before {
background: linear-gradient(135deg, #00c853, #1b5e20, #69f0ae, #1b5e20, #00c853);
background-size: 300% 300%;
}
.scan-wrapper.has-error::before {
background: linear-gradient(135deg, #ff1744, #b71c1c, #ff6e6e, #b71c1c, #ff1744);
background-size: 300% 300%;
}
@keyframes borderGlow {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
/* ===== INNER CONTAINER ===== */
.scan-inner {
position: relative;
z-index: 1;
background: #050f20;
border-radius: 18px;
padding: 16px 20px 20px;
overflow: hidden;
}
/* Ligne de scan animée */
.scan-inner::after {
content: '';
position: absolute;
left: 0; right: 0;
height: 2px;
background: linear-gradient(90deg, transparent 0%, #00e5ff 30%, #ffffff 50%, #00e5ff 70%, transparent 100%);
animation: scanLine 2.5s ease-in-out infinite;
pointer-events: none;
z-index: 2;
opacity: 0.7;
}
@keyframes scanLine {
0% { top: 0%; opacity: 0; }
5% { opacity: 0.8; }
95% { opacity: 0.8; }
100% { top: 100%; opacity: 0; }
}
/* ===== COINS DÉCORATIFS ===== */
.scan-corner {
position: absolute;
width: 20px; height: 20px;
border-color: #00c9ff;
border-style: solid;
z-index: 4;
transition: border-color 0.3s;
}
.scan-corner.tl { top: 10px; left: 10px; border-width: 2px 0 0 2px; border-radius: 4px 0 0 0; }
.scan-corner.tr { top: 10px; right: 10px; border-width: 2px 2px 0 0; border-radius: 0 4px 0 0; }
.scan-corner.bl { bottom: 10px; left: 10px; border-width: 0 0 2px 2px; border-radius: 0 0 0 4px; }
.scan-corner.br { bottom: 10px; right: 10px; border-width: 0 2px 2px 0; border-radius: 0 0 4px 0; }
.scan-wrapper.has-value .scan-corner { border-color: #00c853; }
.scan-wrapper.has-error .scan-corner { border-color: #ff1744; }
/* ===== LABEL EN-TÊTE ===== */
.scan-label {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
font-family: 'Exo 2', sans-serif;
font-size: 9.5pt;
font-weight: 600;
letter-spacing: 3px;
text-transform: uppercase;
color: rgba(0, 180, 230, 0.6);
margin-bottom: 6px;
}
.nfc-icon-pulse {
animation: pulse 2.2s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 0.35; transform: scale(1); }
50% { opacity: 1; transform: scale(1.2); }
}
/* ===== CHAMP INPUT ===== */
#donneesCarte {
font-family: 'Rajdhani', monospace !important;
font-size: 24pt !important;
font-weight: 700;
height: 130px !important;
width: 100% !important;
text-align: center;
/* Données masquées (sécurité) */
color: transparent !important;
text-shadow: 0 0 10px rgba(0, 229, 255, 0.8) !important;
caret-color: #00e5ff;
/* Fond & bordure */
background: transparent !important;
border: none !important;
outline: none !important;
box-shadow: none !important;
letter-spacing: 4px;
position: relative;
z-index: 3;
transition: text-shadow 0.3s;
}
#donneesCarte::placeholder {
font-family: 'Exo 2', sans-serif !important;
font-size: 20pt !important;
font-weight: 600;
color: rgba(0, 210, 255, 0.85) !important;
text-shadow: 0 0 12px rgba(0, 210, 255, 0.5) !important;
letter-spacing: 1px;
}
#donneesCarte:focus::placeholder {
color: rgba(0, 210, 255, 0.55) !important;
text-shadow: 0 0 8px rgba(0, 210, 255, 0.3) !important;
}
/* État : données détectées */
#donneesCarte.reading {
text-shadow: 0 0 14px rgba(0, 200, 83, 0.9) !important;
animation: none !important;
}

View File

@ -1,330 +1,189 @@
<?php
$this->titre = "INTER-SANTE - "._("Recherche du patient par carte") ;
?>
<style>
@import url('https://fonts.googleapis.com/css2?family=Rajdhani:wght@500;700&family=Exo+2:wght@400;600&display=swap');
/* ===== WRAPPER & BORDURE ANIMÉE ===== */
.scan-wrapper {
position: relative;
width: 100%;
max-width: 720px;
margin: 40px auto;
}
.scan-wrapper::before {
content: '';
position: absolute;
inset: -3px;
border-radius: 20px;
background: linear-gradient(135deg, #00c9ff, #0055a5, #00e5ff, #0055a5, #00c9ff);
background-size: 300% 300%;
animation: borderGlow 3s linear infinite;
z-index: 0;
}
.scan-wrapper.has-value::before {
background: linear-gradient(135deg, #00c853, #1b5e20, #69f0ae, #1b5e20, #00c853);
background-size: 300% 300%;
}
.scan-wrapper.has-error::before {
background: linear-gradient(135deg, #ff1744, #b71c1c, #ff6e6e, #b71c1c, #ff1744);
background-size: 300% 300%;
}
@keyframes borderGlow {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
/* ===== INNER CONTAINER ===== */
.scan-inner {
position: relative;
z-index: 1;
background: #050f20;
border-radius: 18px;
padding: 16px 20px 20px;
overflow: hidden;
}
/* Ligne de scan animée */
.scan-inner::after {
content: '';
position: absolute;
left: 0; right: 0;
height: 2px;
background: linear-gradient(90deg, transparent 0%, #00e5ff 30%, #ffffff 50%, #00e5ff 70%, transparent 100%);
animation: scanLine 2.5s ease-in-out infinite;
pointer-events: none;
z-index: 2;
opacity: 0.7;
}
@keyframes scanLine {
0% { top: 0%; opacity: 0; }
5% { opacity: 0.8; }
95% { opacity: 0.8; }
100% { top: 100%; opacity: 0; }
}
/* ===== COINS DÉCORATIFS ===== */
.scan-corner {
position: absolute;
width: 20px; height: 20px;
border-color: #00c9ff;
border-style: solid;
z-index: 4;
transition: border-color 0.3s;
}
.scan-corner.tl { top: 10px; left: 10px; border-width: 2px 0 0 2px; border-radius: 4px 0 0 0; }
.scan-corner.tr { top: 10px; right: 10px; border-width: 2px 2px 0 0; border-radius: 0 4px 0 0; }
.scan-corner.bl { bottom: 10px; left: 10px; border-width: 0 0 2px 2px; border-radius: 0 0 0 4px; }
.scan-corner.br { bottom: 10px; right: 10px; border-width: 0 2px 2px 0; border-radius: 0 0 4px 0; }
.scan-wrapper.has-value .scan-corner { border-color: #00c853; }
.scan-wrapper.has-error .scan-corner { border-color: #ff1744; }
/* ===== LABEL EN-TÊTE ===== */
.scan-label {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
font-family: 'Exo 2', sans-serif;
font-size: 9.5pt;
font-weight: 600;
letter-spacing: 3px;
text-transform: uppercase;
color: rgba(0, 180, 230, 0.6);
margin-bottom: 6px;
}
.nfc-icon-pulse {
animation: pulse 2.2s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 0.35; transform: scale(1); }
50% { opacity: 1; transform: scale(1.2); }
}
/* ===== CHAMP INPUT ===== */
#donneesCarte {
font-family: 'Rajdhani', monospace !important;
font-size: 24pt !important;
font-weight: 700;
height: 130px !important;
width: 100% !important;
text-align: center;
/* Données masquées (sécurité) */
color: transparent !important;
text-shadow: 0 0 10px rgba(0, 229, 255, 0.8) !important;
caret-color: #00e5ff;
/* Fond & bordure */
background: transparent !important;
border: none !important;
outline: none !important;
box-shadow: none !important;
letter-spacing: 4px;
position: relative;
z-index: 3;
transition: text-shadow 0.3s;
}
#donneesCarte::placeholder {
font-family: 'Exo 2', sans-serif !important;
font-size: 20pt !important;
font-weight: 600;
color: rgba(0, 210, 255, 0.85) !important;
text-shadow: 0 0 12px rgba(0, 210, 255, 0.5) !important;
letter-spacing: 1px;
}
#donneesCarte:focus::placeholder {
color: rgba(0, 210, 255, 0.55) !important;
text-shadow: 0 0 8px rgba(0, 210, 255, 0.3) !important;
}
/* État : données détectées */
#donneesCarte.reading {
text-shadow: 0 0 14px rgba(0, 200, 83, 0.9) !important;
animation: none !important;
}
/* ===== INDICATEUR DE LECTURE ===== */
.reading-indicator {
display: none;
text-align: center;
margin-top: 6px;
font-family: 'Exo 2', sans-serif;
font-size: 11pt;
font-weight: 600;
letter-spacing: 2px;
color: #00c853;
text-transform: uppercase;
animation: fadeInUp 0.3s ease;
}
.reading-indicator.active {
display: block;
}
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(6px); }
to { opacity: 1; transform: translateY(0); }
}
/* ===== SPINNER WAIT ===== */
#div_wait_nfc {
min-height: 0;
transition: min-height 0.3s;
}
/* ===== MESSAGE ERREUR ===== */
.alert-danger {
max-width: 720px;
margin: 16px auto 0;
border-radius: 10px;
border: 1px solid #ff1744;
background: rgba(183, 28, 28, 0.15);
color: #ff6e6e;
text-align: center;
padding: 10px 16px;
}
</style>
<form id="frmrechercheparcarte" name="frmrechercheparcarte" method="post" action="Rechercheparcarte/index/">
<div class="scan-wrapper" id="scan-wrapper">
<div class="scan-inner">
<!-- Coins décoratifs -->
<div class="scan-corner tl"></div>
<div class="scan-corner tr"></div>
<div class="scan-corner bl"></div>
<div class="scan-corner br"></div>
<!-- Label -->
<div class="scan-label">
<svg class="nfc-icon-pulse" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#00c9ff" stroke-width="2" stroke-linecap="round">
<path d="M20 6a10 10 0 0 1 0 12"/>
<path d="M17 9a6 6 0 0 1 0 6"/>
<path d="M14 11.5a2 2 0 0 1 0 1"/>
<line x1="4" y1="12" x2="4.01" y2="12"/>
</svg>
INTER-SANTÉ &nbsp;·&nbsp; NFC / QR Code
<div class="page-content animate__animated animate__fadeIn">
<div class="header-section mb-5">
<div class="d-flex align-items-center bg-white p-3 shadow-sm border-start border-primary border-4" style="border-radius: var(--radius-md);">
<div class="icon-shape bg-primary-ghost text-primary rounded-circle me-3" style="width: 48px; height: 48px; display: flex; align-items: center; justify-content: center;">
<i class="fas fa-id-card fs-4"></i>
</div>
<!-- Champ principal -->
<INPUT
class="form-control"
TYPE="text"
id="donneesCarte"
name="donneesCarte"
autofocus
AUTOCOMPLETE="OFF"
placeholder="<?= _("Veuillez scanner la carte NFC ou le QR code!")?>">
<!-- Indicateur -->
<div class="reading-indicator" id="reading-indicator">
<i class="fa fa-check-circle"></i>&nbsp;
<span id="indicator-text"><?= _("Carte détectée") ?></span>
<div>
<h4 id="titre-page" class="mb-0 fw-bold text-uppercase"><?= _("Identification de l'Assuré") ?></h4>
<p class="text-muted small mb-0"><?= _("Scannez une carte NFC ou un QR Code pour identification immédiate") ?></p>
</div>
</div>
</div>
<input id="lancerrechercheparcarte" name="lancerrechercheparcarte" class="sr-only" type="submit" value="<?= _("Rechercher") ?>">
</form>
<div class="row justify-content-center">
<div class="col-md-8 col-lg-6">
<form id="frmrechercheparcarte" name="frmrechercheparcarte" method="post" action="Rechercheparcarte/index/">
<div class="scan-container text-center">
<div class="scan-wrapper shadow-lg mb-4" id="scan-wrapper">
<div class="scan-inner p-5">
<div class="scan-corner tl"></div>
<div class="scan-corner tr"></div>
<div class="scan-corner bl"></div>
<div class="scan-corner br"></div>
<div id="div_wait_nfc"></div>
<div class="scan-interface py-4">
<div class="mb-4">
<div class="nfc-pulse-container mb-3">
<svg class="nfc-icon-pulse" width="60" height="60" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<path d="M20 6a10 10 0 0 1 0 12"/>
<path d="M17 9a6 6 0 0 1 0 6"/>
<path d="M14 11.5a2 2 0 0 1 0 1"/>
<rect x="2" y="6" width="8" height="12" rx="1"/>
</svg>
</div>
<h6 class="text-uppercase fw-bold text-primary ls-2">Inter-Santé NFC / QR</h6>
</div>
<?php if (isset($msgErreur) && $msgErreur > " "): ?>
<div class="alert alert-danger">
<H4><?= $msgErreur ?></H4>
<div class="input-focus-wrapper">
<input
class="form-control-scan"
type="text"
id="donneesCarte"
name="donneesCarte"
autofocus
autocomplete="OFF"
placeholder="<?= _("En attente de détection...") ?>">
</div>
<div class="reading-indicator mt-4" id="reading-indicator">
<div class="d-flex align-items-center justify-content-center text-success">
<i class="fas fa-circle-notch fa-spin me-2"></i>
<span id="indicator-text" class="fw-bold text-uppercase small ls-1"><?= _("Prêt pour la lecture") ?></span>
</div>
</div>
</div>
</div>
</div>
<p class="text-muted small animate__animated animate__pulse animate__infinite">
<i class="fas fa-info-circle me-1"></i> <?= _("Approchez la carte du lecteur ou présentez le QR code à la caméra") ?>
</p>
</div>
<input id="lancerrechercheparcarte" name="lancerrechercheparcarte" class="sr-only" type="submit">
</form>
<div id="div_wait_nfc"></div>
<?php if (isset($msgErreur) && $msgErreur > " "): ?>
<div class="alert alert-danger-ghost border-0 rounded-pill text-center mt-4 animate__animated animate__shakeX">
<i class="fas fa-exclamation-triangle me-2"></i> <strong><?= $msgErreur ?></strong>
</div>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
</div>
<style>
/* Style Spécifique Scan Neutral Pro */
.scan-wrapper {
background: #ffffff;
border-radius: 24px;
position: relative;
overflow: hidden;
border: 2px solid #f1f4f6;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
.scan-wrapper.has-value { border-color: var(--bs-primary); background: rgba(33, 46, 83, 0.02); }
.scan-wrapper.has-error { border-color: var(--bs-danger); background: rgba(220, 53, 69, 0.02); }
.scan-corner {
position: absolute; width: 30px; height: 30px;
border: 4px solid var(--bs-primary);
opacity: 0.3; transition: all 0.3s ease;
}
.tl { top: 20px; left: 20px; border-right: 0; border-bottom: 0; border-top-left-radius: 12px; }
.tr { top: 20px; right: 20px; border-left: 0; border-bottom: 0; border-top-right-radius: 12px; }
.bl { bottom: 20px; left: 20px; border-right: 0; border-top: 0; border-bottom-left-radius: 12px; }
.br { bottom: 20px; right: 20px; border-left: 0; border-top: 0; border-bottom-right-radius: 12px; }
.scan-wrapper.has-value .scan-corner { opacity: 1; width: 45px; height: 45px; }
.form-control-scan {
background: transparent; border: none; text-align: center;
font-size: 1.2rem; font-weight: 600; color: var(--bs-primary);
width: 100%; outline: none; caret-color: var(--bs-primary);
}
.nfc-icon-pulse { color: var(--bs-primary); }
.ls-2 { letter-spacing: 2px; }
.ls-1 { letter-spacing: 1px; }
.alert-danger-ghost {
background-color: rgba(220, 53, 69, 0.1);
color: #dc3545;
padding: 12px 24px;
}
/* Animation de pulsation NFC */
@keyframes nfcPulse {
0% { transform: scale(1); opacity: 0.8; }
50% { transform: scale(1.1); opacity: 1; color: #00c9ff; }
100% { transform: scale(1); opacity: 0.8; }
}
.nfc-icon-pulse { animation: nfcPulse 2s infinite ease-in-out; }
/* ===== INDICATEUR DE LECTURE NEUTRAL PRO ===== */
.reading-indicator {
display: none;
text-align: center;
margin-top: 15px; /* Un peu plus d'espace */
font-family: 'Exo 2', sans-serif;
font-size: 10pt; /* Légèrement plus petit pour plus de finesse */
font-weight: 700;
letter-spacing: 1.5px;
color: #198754; /* Vert standard sécurisant */
text-transform: uppercase;
animation: fadeInUp 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
.reading-indicator.active {
display: flex; /* Flex pour aligner l'icône et le texte */
align-items: center;
justify-content: center;
}
/* ===== SPINNER WAIT ===== */
#div_wait_nfc {
min-height: 0;
transition: all 0.5s ease-in-out;
}
/* ===== MESSAGE ERREUR NEUTRAL PRO ===== */
.alert-danger-ghost {
max-width: 600px; /* Plus compact */
margin: 20px auto 0;
border-radius: 50px; /* Forme pilule comme nos boutons */
border: none;
background: rgba(220, 53, 69, 0.08); /* Rouge très translucide */
color: #dc3545; /* Rouge standard */
text-align: center;
padding: 12px 25px;
font-weight: 600;
font-size: 10pt;
}
</style>
<script>
const inputField = document.getElementById('donneesCarte');
const indicator = document.getElementById('reading-indicator');
const indicatorText = document.getElementById('indicator-text');
const scanWrapper = document.getElementById('scan-wrapper');
// [Garder votre logique JS existante, elle est excellente]
// Mise à jour mineure pour le feedback visuel Neutral Pro
const inputField = document.getElementById('donneesCarte');
const scanWrapper = document.getElementById('scan-wrapper');
const indicatorText = document.getElementById('indicator-text');
// ===== SÉCURITÉ : empêcher copie/coller =====
inputField.addEventListener('contextmenu', e => e.preventDefault());
inputField.addEventListener('copy', e => e.preventDefault());
inputField.addEventListener('cut', e => e.preventDefault());
inputField.addEventListener('input', function() {
if (this.value.length > 0) {
scanWrapper.classList.add('has-value');
indicatorText.textContent = this.value.endsWith('qr') ? '<?= _("QR Code détecté...") ?>' : '<?= _("Carte détectée...") ?>';
} else {
scanWrapper.classList.remove('has-value');
indicatorText.textContent = '<?= _("En attente de détection...") ?>';
}
});
// ===== MAINTIEN DU FOCUS =====
inputField.addEventListener('blur', function() {
setTimeout(() => this.focus(), 10);
});
const focusInterval = setInterval(function() {
if (document.activeElement !== inputField && !document.hidden) {
inputField.focus();
}
}, 200);
document.addEventListener('keydown', function(e) {
if (!e.ctrlKey && !e.altKey && !e.metaKey && document.activeElement !== inputField) {
inputField.focus();
}
});
document.getElementById('frmrechercheparcarte').addEventListener('submit', function() {
clearInterval(focusInterval);
});
// ===== DÉTECTION DU TYPE =====
function detectScanType(value) {
return value.endsWith('qr') ? 'qr' : 'nfc';
}
// ===== FEEDBACK VISUEL À LA SAISIE =====
inputField.addEventListener('input', function() {
if (this.value.length > 0) {
this.classList.add('reading');
indicator.classList.add('active');
scanWrapper.classList.add('has-value');
scanWrapper.classList.remove('has-error');
const type = detectScanType(this.value);
indicatorText.textContent = type === 'qr'
? '<?= _("QR code détecté") ?>'
: '<?= _("Carte détectée") ?>';
} else {
this.classList.remove('reading');
indicator.classList.remove('active');
scanWrapper.classList.remove('has-value', 'has-error');
}
});
// ===== SOUMISSION AUTO =====
inputField.addEventListener('change', function() {
if (this.value.length > 3) {
const type = detectScanType(this.value);
const message = type === 'qr'
? '<?= _("Traitement du QR code...") ?>'
: '<?= _("Lecture de la carte...") ?>';
$('#div_wait_nfc').html(
'<div style="padding-top:60px; text-align:center; font-family:\'Exo 2\',sans-serif; color:#00c9ff;">'
+ '<i class="fa fa-spinner fa-spin fa-4x"></i>'
+ '<p style="margin-top:20px; font-size:13pt; letter-spacing:2px; text-transform:uppercase;">' + message + '</p>'
+ '</div>'
);
this.form.submit();
}
});
// ===== FOCUS INITIAL =====
window.addEventListener('load', () => inputField.focus());
document.addEventListener('DOMContentLoaded', () => inputField.focus());
// ===== Afficher erreur en rouge si présente =====
<?php if (isset($msgErreur) && $msgErreur > " "): ?>
scanWrapper.classList.add('has-error');
<?php endif; ?>
</script>
// Forcer le focus 200ms après chargement (Règle Neutral Pro)
setTimeout(() => inputField.focus(), 200);
</script>