805 lines
28 KiB
HTML
805 lines
28 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Glass Morphism Input Intelligence</title>
|
|
<style>
|
|
:root {
|
|
--glass-bg: rgba(255, 255, 255, 0.1);
|
|
--glass-border: rgba(255, 255, 255, 0.2);
|
|
--glass-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
|
--glass-blur: blur(10px);
|
|
--text-primary: #1a1a1a;
|
|
--text-secondary: #666;
|
|
--accent-valid: #22c55e;
|
|
--accent-invalid: #ef4444;
|
|
--accent-info: #3b82f6;
|
|
--gradient-edge: linear-gradient(135deg,
|
|
rgba(255, 255, 255, 0.3) 0%,
|
|
rgba(255, 255, 255, 0.1) 100%);
|
|
}
|
|
|
|
@media (prefers-color-scheme: dark) {
|
|
:root {
|
|
--glass-bg: rgba(255, 255, 255, 0.05);
|
|
--glass-border: rgba(255, 255, 255, 0.1);
|
|
--glass-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
|
--text-primary: #f5f5f5;
|
|
--text-secondary: #a8a8a8;
|
|
}
|
|
}
|
|
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
min-height: 100vh;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 20px;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
main {
|
|
width: 100%;
|
|
max-width: 600px;
|
|
}
|
|
|
|
h1 {
|
|
text-align: center;
|
|
margin-bottom: 40px;
|
|
color: white;
|
|
font-weight: 300;
|
|
font-size: 2.5em;
|
|
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
|
|
}
|
|
|
|
.input-intelligence {
|
|
position: relative;
|
|
margin-bottom: 40px;
|
|
}
|
|
|
|
.glass-container {
|
|
background: var(--glass-bg);
|
|
backdrop-filter: var(--glass-blur);
|
|
-webkit-backdrop-filter: var(--glass-blur);
|
|
border-radius: 16px;
|
|
border: 1px solid var(--glass-border);
|
|
box-shadow: var(--glass-shadow);
|
|
padding: 24px;
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.glass-container::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 1px;
|
|
background: var(--gradient-edge);
|
|
}
|
|
|
|
.input-wrapper {
|
|
position: relative;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.glass-input {
|
|
width: 100%;
|
|
padding: 16px 48px 16px 16px;
|
|
font-size: 16px;
|
|
background: rgba(255, 255, 255, 0.05);
|
|
border: 1px solid var(--glass-border);
|
|
border-radius: 12px;
|
|
color: var(--text-primary);
|
|
backdrop-filter: blur(5px);
|
|
-webkit-backdrop-filter: blur(5px);
|
|
transition: all 0.3s ease;
|
|
outline: none;
|
|
}
|
|
|
|
.glass-input:focus {
|
|
background: rgba(255, 255, 255, 0.08);
|
|
border-color: rgba(255, 255, 255, 0.3);
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.input-status {
|
|
position: absolute;
|
|
right: 16px;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
width: 24px;
|
|
height: 24px;
|
|
opacity: 0;
|
|
transition: opacity 0.3s ease;
|
|
}
|
|
|
|
.input-status.show {
|
|
opacity: 1;
|
|
}
|
|
|
|
.status-icon {
|
|
width: 100%;
|
|
height: 100%;
|
|
stroke-width: 2;
|
|
}
|
|
|
|
.status-icon.valid {
|
|
stroke: var(--accent-valid);
|
|
}
|
|
|
|
.status-icon.invalid {
|
|
stroke: var(--accent-invalid);
|
|
}
|
|
|
|
.status-icon.loading {
|
|
stroke: var(--accent-info);
|
|
animation: rotate 1s linear infinite;
|
|
}
|
|
|
|
@keyframes rotate {
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
|
|
.help-tooltip {
|
|
position: absolute;
|
|
top: calc(100% + 8px);
|
|
left: 0;
|
|
right: 0;
|
|
background: var(--glass-bg);
|
|
backdrop-filter: var(--glass-blur);
|
|
-webkit-backdrop-filter: var(--glass-blur);
|
|
border: 1px solid var(--glass-border);
|
|
border-radius: 12px;
|
|
padding: 12px 16px;
|
|
box-shadow: var(--glass-shadow);
|
|
opacity: 0;
|
|
transform: translateY(-10px);
|
|
transition: all 0.3s ease;
|
|
pointer-events: none;
|
|
z-index: 10;
|
|
}
|
|
|
|
.help-tooltip.show {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
pointer-events: auto;
|
|
}
|
|
|
|
.help-tooltip::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: -6px;
|
|
left: 24px;
|
|
width: 12px;
|
|
height: 12px;
|
|
background: var(--glass-bg);
|
|
border: 1px solid var(--glass-border);
|
|
border-right: none;
|
|
border-bottom: none;
|
|
transform: rotate(45deg);
|
|
}
|
|
|
|
.help-text {
|
|
font-size: 14px;
|
|
color: var(--text-secondary);
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.autocomplete-dropdown {
|
|
position: absolute;
|
|
top: calc(100% + 8px);
|
|
left: 0;
|
|
right: 0;
|
|
background: var(--glass-bg);
|
|
backdrop-filter: var(--glass-blur);
|
|
-webkit-backdrop-filter: var(--glass-blur);
|
|
border: 1px solid var(--glass-border);
|
|
border-radius: 12px;
|
|
box-shadow: var(--glass-shadow);
|
|
opacity: 0;
|
|
transform: translateY(-10px) scale(0.98);
|
|
transition: all 0.3s ease;
|
|
pointer-events: none;
|
|
z-index: 20;
|
|
max-height: 240px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.autocomplete-dropdown.show {
|
|
opacity: 1;
|
|
transform: translateY(0) scale(1);
|
|
pointer-events: auto;
|
|
}
|
|
|
|
.autocomplete-item {
|
|
padding: 12px 16px;
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.autocomplete-item:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.autocomplete-item::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: -100%;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: rgba(255, 255, 255, 0.1);
|
|
transition: left 0.3s ease;
|
|
}
|
|
|
|
.autocomplete-item:hover::before,
|
|
.autocomplete-item.selected::before {
|
|
left: 0;
|
|
}
|
|
|
|
.autocomplete-item.selected {
|
|
background: rgba(255, 255, 255, 0.05);
|
|
}
|
|
|
|
.autocomplete-primary {
|
|
color: var(--text-primary);
|
|
font-weight: 500;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.autocomplete-secondary {
|
|
color: var(--text-secondary);
|
|
font-size: 12px;
|
|
}
|
|
|
|
.format-hint {
|
|
position: absolute;
|
|
top: 50%;
|
|
right: 48px;
|
|
transform: translateY(-50%);
|
|
font-size: 12px;
|
|
color: var(--text-secondary);
|
|
opacity: 0;
|
|
transition: opacity 0.3s ease;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.format-hint.show {
|
|
opacity: 0.7;
|
|
}
|
|
|
|
.demo-section {
|
|
margin-bottom: 32px;
|
|
}
|
|
|
|
.demo-title {
|
|
color: white;
|
|
font-size: 1.2em;
|
|
margin-bottom: 16px;
|
|
font-weight: 300;
|
|
}
|
|
|
|
.feature-list {
|
|
background: var(--glass-bg);
|
|
backdrop-filter: var(--glass-blur);
|
|
-webkit-backdrop-filter: var(--glass-blur);
|
|
border: 1px solid var(--glass-border);
|
|
border-radius: 12px;
|
|
padding: 16px;
|
|
font-size: 14px;
|
|
color: var(--text-secondary);
|
|
line-height: 1.6;
|
|
}
|
|
|
|
.feature-list li {
|
|
margin-left: 20px;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
@media (max-width: 640px) {
|
|
h1 {
|
|
font-size: 2em;
|
|
}
|
|
|
|
.glass-container {
|
|
padding: 20px;
|
|
}
|
|
|
|
.glass-input {
|
|
font-size: 16px;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<main>
|
|
<h1>Input Intelligence - Glass Morphism Theme</h1>
|
|
|
|
<div class="demo-section">
|
|
<h2 class="demo-title">Email Input with Validation</h2>
|
|
<div class="input-intelligence">
|
|
<div class="glass-container">
|
|
<div class="input-wrapper">
|
|
<input
|
|
type="text"
|
|
class="glass-input"
|
|
id="emailInput"
|
|
placeholder="Enter your email address"
|
|
autocomplete="off"
|
|
>
|
|
<span class="format-hint" id="emailFormatHint"></span>
|
|
<div class="input-status" id="emailStatus">
|
|
<svg class="status-icon" viewBox="0 0 24 24" fill="none">
|
|
<!-- Dynamic SVG content inserted by JS -->
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<div class="help-tooltip" id="emailHelp">
|
|
<p class="help-text"></p>
|
|
</div>
|
|
<div class="autocomplete-dropdown" id="emailAutocomplete"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="demo-section">
|
|
<h2 class="demo-title">Phone Number with Smart Formatting</h2>
|
|
<div class="input-intelligence">
|
|
<div class="glass-container">
|
|
<div class="input-wrapper">
|
|
<input
|
|
type="text"
|
|
class="glass-input"
|
|
id="phoneInput"
|
|
placeholder="Enter phone number"
|
|
autocomplete="off"
|
|
>
|
|
<span class="format-hint" id="phoneFormatHint"></span>
|
|
<div class="input-status" id="phoneStatus">
|
|
<svg class="status-icon" viewBox="0 0 24 24" fill="none">
|
|
<!-- Dynamic SVG content inserted by JS -->
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<div class="help-tooltip" id="phoneHelp">
|
|
<p class="help-text"></p>
|
|
</div>
|
|
<div class="autocomplete-dropdown" id="phoneAutocomplete"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="demo-section">
|
|
<h2 class="demo-title">Credit Card with Security Features</h2>
|
|
<div class="input-intelligence">
|
|
<div class="glass-container">
|
|
<div class="input-wrapper">
|
|
<input
|
|
type="text"
|
|
class="glass-input"
|
|
id="cardInput"
|
|
placeholder="Enter card number"
|
|
autocomplete="off"
|
|
>
|
|
<span class="format-hint" id="cardFormatHint"></span>
|
|
<div class="input-status" id="cardStatus">
|
|
<svg class="status-icon" viewBox="0 0 24 24" fill="none">
|
|
<!-- Dynamic SVG content inserted by JS -->
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<div class="help-tooltip" id="cardHelp">
|
|
<p class="help-text"></p>
|
|
</div>
|
|
<div class="autocomplete-dropdown" id="cardAutocomplete"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="feature-list">
|
|
<ul>
|
|
<li>Real-time validation with visual feedback</li>
|
|
<li>Contextual help that adapts to input</li>
|
|
<li>Smart formatting as you type</li>
|
|
<li>Intelligent autocomplete suggestions</li>
|
|
<li>Glass morphism design with depth hierarchy</li>
|
|
<li>Light/dark mode adaptive styling</li>
|
|
</ul>
|
|
</div>
|
|
</main>
|
|
|
|
<script>
|
|
// SVG Icons
|
|
const icons = {
|
|
valid: '<circle cx="12" cy="12" r="10"></circle><path d="M9 12l2 2 4-4"></path>',
|
|
invalid: '<circle cx="12" cy="12" r="10"></circle><path d="M15 9l-6 6"></path><path d="M9 9l6 6"></path>',
|
|
loading: '<circle cx="12" cy="12" r="10" stroke-dasharray="50" stroke-dashoffset="10"></circle>'
|
|
};
|
|
|
|
// Input Intelligence Class
|
|
class InputIntelligence {
|
|
constructor(inputId, config) {
|
|
this.input = document.getElementById(inputId);
|
|
this.status = document.getElementById(inputId.replace('Input', 'Status'));
|
|
this.help = document.getElementById(inputId.replace('Input', 'Help'));
|
|
this.autocomplete = document.getElementById(inputId.replace('Input', 'Autocomplete'));
|
|
this.formatHint = document.getElementById(inputId.replace('Input', 'FormatHint'));
|
|
this.config = config;
|
|
this.selectedIndex = -1;
|
|
this.suggestions = [];
|
|
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
this.input.addEventListener('input', this.handleInput.bind(this));
|
|
this.input.addEventListener('focus', this.handleFocus.bind(this));
|
|
this.input.addEventListener('blur', this.handleBlur.bind(this));
|
|
this.input.addEventListener('keydown', this.handleKeydown.bind(this));
|
|
|
|
this.autocomplete.addEventListener('click', this.handleAutocompleteClick.bind(this));
|
|
}
|
|
|
|
handleInput(e) {
|
|
const value = e.target.value;
|
|
|
|
// Clear previous timeout
|
|
clearTimeout(this.validationTimeout);
|
|
clearTimeout(this.autocompleteTimeout);
|
|
|
|
// Show loading state
|
|
this.showStatus('loading');
|
|
|
|
// Format input
|
|
if (this.config.formatter) {
|
|
const formatted = this.config.formatter(value);
|
|
if (formatted !== value) {
|
|
e.target.value = formatted;
|
|
this.showFormatHint(this.config.formatHint);
|
|
}
|
|
}
|
|
|
|
// Validate after delay
|
|
this.validationTimeout = setTimeout(() => {
|
|
this.validate(e.target.value);
|
|
}, 500);
|
|
|
|
// Show autocomplete after delay
|
|
if (value.length >= this.config.minLength) {
|
|
this.autocompleteTimeout = setTimeout(() => {
|
|
this.showAutocomplete(value);
|
|
}, 300);
|
|
} else {
|
|
this.hideAutocomplete();
|
|
}
|
|
}
|
|
|
|
handleFocus() {
|
|
if (this.input.value.length >= this.config.minLength) {
|
|
this.showAutocomplete(this.input.value);
|
|
}
|
|
this.showHelp(this.config.initialHelp);
|
|
}
|
|
|
|
handleBlur(e) {
|
|
// Delay to allow autocomplete clicks
|
|
setTimeout(() => {
|
|
if (!this.autocomplete.contains(e.relatedTarget)) {
|
|
this.hideAutocomplete();
|
|
this.hideHelp();
|
|
this.hideFormatHint();
|
|
}
|
|
}, 200);
|
|
}
|
|
|
|
handleKeydown(e) {
|
|
if (!this.autocomplete.classList.contains('show')) return;
|
|
|
|
const items = this.autocomplete.querySelectorAll('.autocomplete-item');
|
|
|
|
switch(e.key) {
|
|
case 'ArrowDown':
|
|
e.preventDefault();
|
|
this.selectedIndex = Math.min(this.selectedIndex + 1, items.length - 1);
|
|
this.updateSelection(items);
|
|
break;
|
|
case 'ArrowUp':
|
|
e.preventDefault();
|
|
this.selectedIndex = Math.max(this.selectedIndex - 1, -1);
|
|
this.updateSelection(items);
|
|
break;
|
|
case 'Enter':
|
|
if (this.selectedIndex >= 0 && this.selectedIndex < items.length) {
|
|
e.preventDefault();
|
|
this.selectSuggestion(this.suggestions[this.selectedIndex]);
|
|
}
|
|
break;
|
|
case 'Escape':
|
|
this.hideAutocomplete();
|
|
break;
|
|
}
|
|
}
|
|
|
|
handleAutocompleteClick(e) {
|
|
const item = e.target.closest('.autocomplete-item');
|
|
if (item) {
|
|
const index = Array.from(item.parentNode.children).indexOf(item);
|
|
this.selectSuggestion(this.suggestions[index]);
|
|
}
|
|
}
|
|
|
|
validate(value) {
|
|
if (!value) {
|
|
this.hideStatus();
|
|
this.showHelp(this.config.initialHelp);
|
|
return;
|
|
}
|
|
|
|
const result = this.config.validator(value);
|
|
|
|
if (result.valid) {
|
|
this.showStatus('valid');
|
|
this.showHelp(result.help || this.config.validHelp);
|
|
} else {
|
|
this.showStatus('invalid');
|
|
this.showHelp(result.help || this.config.invalidHelp);
|
|
}
|
|
}
|
|
|
|
showAutocomplete(value) {
|
|
this.suggestions = this.config.getSuggestions(value);
|
|
this.selectedIndex = -1;
|
|
|
|
if (this.suggestions.length === 0) {
|
|
this.hideAutocomplete();
|
|
return;
|
|
}
|
|
|
|
this.autocomplete.innerHTML = this.suggestions.map((suggestion, index) => `
|
|
<div class="autocomplete-item">
|
|
<div class="autocomplete-primary">${suggestion.primary}</div>
|
|
${suggestion.secondary ? `<div class="autocomplete-secondary">${suggestion.secondary}</div>` : ''}
|
|
</div>
|
|
`).join('');
|
|
|
|
this.autocomplete.classList.add('show');
|
|
}
|
|
|
|
hideAutocomplete() {
|
|
this.autocomplete.classList.remove('show');
|
|
this.selectedIndex = -1;
|
|
}
|
|
|
|
updateSelection(items) {
|
|
items.forEach((item, index) => {
|
|
item.classList.toggle('selected', index === this.selectedIndex);
|
|
});
|
|
}
|
|
|
|
selectSuggestion(suggestion) {
|
|
this.input.value = suggestion.value;
|
|
this.hideAutocomplete();
|
|
this.validate(suggestion.value);
|
|
this.input.focus();
|
|
}
|
|
|
|
showStatus(type) {
|
|
const icon = this.status.querySelector('.status-icon');
|
|
icon.innerHTML = icons[type];
|
|
icon.className = `status-icon ${type}`;
|
|
this.status.classList.add('show');
|
|
}
|
|
|
|
hideStatus() {
|
|
this.status.classList.remove('show');
|
|
}
|
|
|
|
showHelp(text) {
|
|
this.help.querySelector('.help-text').textContent = text;
|
|
this.help.classList.add('show');
|
|
}
|
|
|
|
hideHelp() {
|
|
this.help.classList.remove('show');
|
|
}
|
|
|
|
showFormatHint(text) {
|
|
this.formatHint.textContent = text;
|
|
this.formatHint.classList.add('show');
|
|
|
|
clearTimeout(this.formatHintTimeout);
|
|
this.formatHintTimeout = setTimeout(() => {
|
|
this.hideFormatHint();
|
|
}, 2000);
|
|
}
|
|
|
|
hideFormatHint() {
|
|
this.formatHint.classList.remove('show');
|
|
}
|
|
}
|
|
|
|
// Email configuration
|
|
const emailConfig = {
|
|
minLength: 3,
|
|
initialHelp: 'Enter a valid email address',
|
|
validHelp: 'Email address looks good!',
|
|
invalidHelp: 'Please enter a valid email format',
|
|
formatHint: 'Auto-formatted',
|
|
validator: (value) => {
|
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
return {
|
|
valid: emailRegex.test(value),
|
|
help: value.includes('@') && !value.includes('.') ?
|
|
'Don\'t forget the domain extension (e.g., .com)' : null
|
|
};
|
|
},
|
|
formatter: (value) => {
|
|
return value.toLowerCase().replace(/\s/g, '');
|
|
},
|
|
getSuggestions: (value) => {
|
|
const domains = [
|
|
{ domain: 'gmail.com', provider: 'Google' },
|
|
{ domain: 'outlook.com', provider: 'Microsoft' },
|
|
{ domain: 'yahoo.com', provider: 'Yahoo' },
|
|
{ domain: 'icloud.com', provider: 'Apple' },
|
|
{ domain: 'protonmail.com', provider: 'ProtonMail' }
|
|
];
|
|
|
|
if (value.includes('@') && !value.split('@')[1]) {
|
|
const [username] = value.split('@');
|
|
return domains.map(d => ({
|
|
primary: `${username}@${d.domain}`,
|
|
secondary: `${d.provider} email`,
|
|
value: `${username}@${d.domain}`
|
|
}));
|
|
}
|
|
|
|
return [];
|
|
}
|
|
};
|
|
|
|
// Phone configuration
|
|
const phoneConfig = {
|
|
minLength: 3,
|
|
initialHelp: 'Enter a phone number',
|
|
validHelp: 'Phone number is valid',
|
|
invalidHelp: 'Please enter a valid phone number',
|
|
formatHint: 'Auto-formatted',
|
|
validator: (value) => {
|
|
const digits = value.replace(/\D/g, '');
|
|
return {
|
|
valid: digits.length >= 10 && digits.length <= 15,
|
|
help: digits.length < 10 ? 'Phone number too short' :
|
|
digits.length > 15 ? 'Phone number too long' : null
|
|
};
|
|
},
|
|
formatter: (value) => {
|
|
const digits = value.replace(/\D/g, '');
|
|
|
|
if (digits.length <= 3) return digits;
|
|
if (digits.length <= 6) return `(${digits.slice(0, 3)}) ${digits.slice(3)}`;
|
|
if (digits.length <= 10) return `(${digits.slice(0, 3)}) ${digits.slice(3, 6)}-${digits.slice(6)}`;
|
|
|
|
return `+${digits.slice(0, digits.length - 10)} (${digits.slice(-10, -7)}) ${digits.slice(-7, -4)}-${digits.slice(-4)}`;
|
|
},
|
|
getSuggestions: (value) => {
|
|
const countryCodes = [
|
|
{ code: '+1', country: 'United States / Canada' },
|
|
{ code: '+44', country: 'United Kingdom' },
|
|
{ code: '+33', country: 'France' },
|
|
{ code: '+49', country: 'Germany' },
|
|
{ code: '+81', country: 'Japan' }
|
|
];
|
|
|
|
if (value === '+' || value.startsWith('+')) {
|
|
return countryCodes
|
|
.filter(c => c.code.startsWith(value))
|
|
.map(c => ({
|
|
primary: c.code,
|
|
secondary: c.country,
|
|
value: c.code + ' '
|
|
}));
|
|
}
|
|
|
|
return [];
|
|
}
|
|
};
|
|
|
|
// Credit card configuration
|
|
const cardConfig = {
|
|
minLength: 4,
|
|
initialHelp: 'Enter your credit card number',
|
|
validHelp: 'Card number is valid',
|
|
invalidHelp: 'Please enter a valid card number',
|
|
formatHint: 'Formatting applied',
|
|
validator: (value) => {
|
|
const digits = value.replace(/\s/g, '');
|
|
|
|
// Basic Luhn algorithm check
|
|
if (digits.length < 13 || digits.length > 19) {
|
|
return { valid: false };
|
|
}
|
|
|
|
let sum = 0;
|
|
let isEven = false;
|
|
|
|
for (let i = digits.length - 1; i >= 0; i--) {
|
|
let digit = parseInt(digits[i]);
|
|
|
|
if (isEven) {
|
|
digit *= 2;
|
|
if (digit > 9) digit -= 9;
|
|
}
|
|
|
|
sum += digit;
|
|
isEven = !isEven;
|
|
}
|
|
|
|
return {
|
|
valid: sum % 10 === 0,
|
|
help: digits.length < 16 ? 'Card number incomplete' : null
|
|
};
|
|
},
|
|
formatter: (value) => {
|
|
const digits = value.replace(/\s/g, '');
|
|
const groups = [];
|
|
|
|
// Detect card type and format accordingly
|
|
if (digits.startsWith('34') || digits.startsWith('37')) {
|
|
// American Express: 4-6-5
|
|
groups.push(digits.slice(0, 4), digits.slice(4, 10), digits.slice(10, 15));
|
|
} else {
|
|
// Others: 4-4-4-4
|
|
for (let i = 0; i < digits.length; i += 4) {
|
|
groups.push(digits.slice(i, i + 4));
|
|
}
|
|
}
|
|
|
|
return groups.filter(g => g).join(' ');
|
|
},
|
|
getSuggestions: (value) => {
|
|
const cardTypes = [
|
|
{ prefix: '4', type: 'Visa', icon: '💳' },
|
|
{ prefix: '5', type: 'Mastercard', icon: '💳' },
|
|
{ prefix: '34', type: 'American Express', icon: '💳' },
|
|
{ prefix: '37', type: 'American Express', icon: '💳' },
|
|
{ prefix: '6', type: 'Discover', icon: '💳' }
|
|
];
|
|
|
|
const digits = value.replace(/\s/g, '');
|
|
|
|
if (digits.length <= 2) {
|
|
return cardTypes
|
|
.filter(c => c.prefix.startsWith(digits))
|
|
.map(c => ({
|
|
primary: `${c.icon} ${c.type}`,
|
|
secondary: `Starts with ${c.prefix}`,
|
|
value: c.prefix
|
|
}));
|
|
}
|
|
|
|
return [];
|
|
}
|
|
};
|
|
|
|
// Initialize all inputs
|
|
new InputIntelligence('emailInput', emailConfig);
|
|
new InputIntelligence('phoneInput', phoneConfig);
|
|
new InputIntelligence('cardInput', cardConfig);
|
|
</script>
|
|
</body>
|
|
</html> |