ngx-numeric-captcha
Version:
A lightweight Angular CAPTCHA library with Math, Slider, and Pattern verification types. Compact design perfect for login forms and secure applications.
162 lines (158 loc) • 25.5 kB
JavaScript
import * as i1 from '@angular/common';
import { CommonModule } from '@angular/common';
import * as i0 from '@angular/core';
import { EventEmitter, Output, Input, Component } from '@angular/core';
import * as i2 from '@angular/forms';
import { FormsModule } from '@angular/forms';
var CaptchaType;
(function (CaptchaType) {
CaptchaType["MATH"] = "math";
CaptchaType["SLIDER"] = "slider";
CaptchaType["PATTERN"] = "pattern";
})(CaptchaType || (CaptchaType = {}));
class NumericCaptchaComponent {
type = CaptchaType.MATH;
size = 'small';
captchaResult = new EventEmitter();
// Common properties
userAnswer = '';
isVerified = null;
attempts = 0;
maxAttempts = 3;
// Math CAPTCHA
mathExpression = '';
correctAnswer = 0;
// Slider CAPTCHA
sliderPosition = 0;
targetPosition = 0;
sliderTolerance = 2;
// Pattern CAPTCHA
patternSequence = [];
userPattern = [];
patternSize = 6; // Reduced for compact view
ngOnInit() {
this.initializeCaptcha();
}
initializeCaptcha() {
this.resetState();
switch (this.type) {
case CaptchaType.MATH:
this.generateMathCaptcha();
break;
case CaptchaType.SLIDER:
this.generateSliderCaptcha();
break;
case CaptchaType.PATTERN:
this.generatePatternCaptcha();
break;
}
}
resetState() {
this.userAnswer = '';
this.isVerified = null;
this.userPattern = [];
this.sliderPosition = 0;
}
generateMathCaptcha() {
const operations = ['+', '-', '*'];
const operation = operations[Math.floor(Math.random() * operations.length)];
// Keep numbers small for compact display
const num1 = Math.floor(Math.random() * 15) + 1;
const num2 = Math.floor(Math.random() * 15) + 1;
switch (operation) {
case '+':
this.correctAnswer = num1 + num2;
break;
case '-':
this.correctAnswer = num1 - num2;
break;
case '*':
this.correctAnswer = num1 * num2;
break;
}
this.mathExpression = `${num1} ${operation} ${num2}`;
}
generateSliderCaptcha() {
this.targetPosition = Math.floor(Math.random() * 70) + 15; // 15-85%
this.sliderPosition = 0;
}
generatePatternCaptcha() {
const sequenceLength = 3; // Keep short for compact view
this.patternSequence = Array.from({ length: sequenceLength }, () => Math.floor(Math.random() * this.patternSize));
}
verify() {
this.attempts++;
let isCorrect = false;
switch (this.type) {
case CaptchaType.MATH:
isCorrect = parseInt(this.userAnswer) === this.correctAnswer;
break;
case CaptchaType.SLIDER:
isCorrect = Math.abs(this.sliderPosition - this.targetPosition) <= this.sliderTolerance;
break;
case CaptchaType.PATTERN:
isCorrect = this.arraysEqual(this.userPattern, this.patternSequence);
break;
}
this.isVerified = isCorrect;
this.captchaResult.emit({
isValid: isCorrect,
attempts: this.attempts
});
// Only auto-refresh on wrong answer if not at max attempts
if (!isCorrect && this.attempts < this.maxAttempts) {
setTimeout(() => {
// Don't reset attempts here - just generate new captcha
this.resetState();
this.generateNewCaptcha();
}, 1500);
}
}
generateNewCaptcha() {
switch (this.type) {
case CaptchaType.MATH:
this.generateMathCaptcha();
break;
case CaptchaType.SLIDER:
this.generateSliderCaptcha();
break;
case CaptchaType.PATTERN:
this.generatePatternCaptcha();
break;
}
}
// Pattern methods
addToPattern(index) {
if (this.userPattern.length < this.patternSequence.length) {
this.userPattern.push(index);
}
}
clearPattern() {
this.userPattern = [];
}
// Utility methods
arraysEqual(a, b) {
return a.length === b.length && a.every((val, i) => val === b[i]);
}
refreshCaptcha() {
this.attempts = 0;
this.initializeCaptcha();
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: NumericCaptchaComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.3", type: NumericCaptchaComponent, isStandalone: true, selector: "ngx-numeric-captcha", inputs: { type: "type", size: "size" }, outputs: { captchaResult: "captchaResult" }, ngImport: i0, template: "<div class=\"compact-captcha\" [class.small]=\"size === 'small'\" [class.medium]=\"size === 'medium'\">\r\n \r\n <!-- Math CAPTCHA -->\r\n <div *ngIf=\"type === 'math'\" class=\"math-captcha\">\r\n <div class=\"captcha-row\">\r\n <span class=\"expression\">{{ mathExpression }} =</span>\r\n <input \r\n [(ngModel)]=\"userAnswer\" \r\n type=\"number\" \r\n placeholder=\"?\"\r\n class=\"answer-input\"\r\n [disabled]=\"isVerified !== null\"\r\n (keyup.enter)=\"verify()\"\r\n />\r\n <button class=\"verify-btn\" (click)=\"verify()\" [disabled]=\"isVerified === true\">\r\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\r\n <path d=\"M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z\"/>\r\n </svg>\r\n </button>\r\n <button class=\"refresh-btn\" (click)=\"refreshCaptcha()\" [disabled]=\"isVerified === true\">\r\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\r\n <path d=\"M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z\"/>\r\n </svg>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- Slider CAPTCHA -->\r\n <div *ngIf=\"type === 'slider'\" class=\"slider-captcha\">\r\n <div class=\"slider-header\">\r\n <span class=\"prompt\">Match position</span>\r\n <div class=\"actions\">\r\n <button class=\"verify-btn\" (click)=\"verify()\" [disabled]=\"isVerified === true\">\r\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\r\n <path d=\"M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z\"/>\r\n </svg>\r\n </button>\r\n <button class=\"refresh-btn\" (click)=\"refreshCaptcha()\" [disabled]=\"isVerified === true\">\r\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\r\n <path d=\"M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z\"/>\r\n </svg>\r\n </button>\r\n </div>\r\n </div>\r\n <div class=\"slider-track\">\r\n <div class=\"target-indicator\" [style.left.%]=\"targetPosition\"></div>\r\n <input \r\n type=\"range\" \r\n min=\"0\" \r\n max=\"100\" \r\n [(ngModel)]=\"sliderPosition\"\r\n class=\"slider\"\r\n />\r\n </div>\r\n </div>\r\n\r\n <!-- Pattern CAPTCHA -->\r\n <div *ngIf=\"type === 'pattern'\" class=\"pattern-captcha\">\r\n <div class=\"pattern-header\">\r\n <span class=\"prompt\">Repeat:</span>\r\n <div class=\"pattern-sequence\">\r\n <span *ngFor=\"let item of patternSequence\" class=\"pattern-item\">{{ item + 1 }}</span>\r\n </div>\r\n <div class=\"actions\">\r\n <button class=\"verify-btn\" (click)=\"verify()\" [disabled]=\"isVerified === true\">\r\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\r\n <path d=\"M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z\"/>\r\n </svg>\r\n </button>\r\n <button class=\"refresh-btn\" (click)=\"refreshCaptcha()\" [disabled]=\"isVerified === true\">\r\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\r\n <path d=\"M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z\"/>\r\n </svg>\r\n </button>\r\n </div>\r\n </div>\r\n <div class=\"pattern-grid\">\r\n <button \r\n *ngFor=\"let item of [].constructor(patternSize); let i = index\"\r\n class=\"pattern-button\"\r\n [class.selected]=\"userPattern.includes(i)\"\r\n (click)=\"addToPattern(i)\"\r\n [disabled]=\"userPattern.length >= patternSequence.length\"\r\n >\r\n {{ i + 1 }}\r\n </button>\r\n </div>\r\n <button class=\"clear-btn\" (click)=\"clearPattern()\" *ngIf=\"userPattern.length > 0\">Clear</button>\r\n </div>\r\n\r\n <!-- Status Indicator -->\r\n<div class=\"status-indicator\" *ngIf=\"isVerified !== null\">\r\n <svg *ngIf=\"isVerified\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"#4CAF50\">\r\n <path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z\"/>\r\n </svg>\r\n <svg *ngIf=\"!isVerified\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"#F44336\">\r\n <path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm3.5 6L12 10.5 8.5 8 7 9.5 10.5 12 7 14.5 8.5 16 12 13.5 15.5 16 17 14.5 13.5 12 17 9.5 15.5 8z\"/>\r\n </svg>\r\n <span class=\"status-text\">{{ isVerified ? 'Verified' : 'Try again' }}</span>\r\n <span class=\"attempts\" *ngIf=\"!isVerified && attempts < maxAttempts\">({{ attempts }}/{{ maxAttempts }})</span>\r\n <span class=\"attempts-max\" *ngIf=\"!isVerified && attempts >= maxAttempts\">Max attempts reached</span>\r\n</div>\r\n\r\n \r\n</div>\r\n", styles: [".compact-captcha{border:1px solid #ddd;border-radius:8px;padding:12px;background:#fff;font-family:Segoe UI,Tahoma,Geneva,Verdana,sans-serif;max-width:100%}.compact-captcha.small{padding:8px;font-size:14px}.compact-captcha.medium{padding:12px;font-size:15px}.math-captcha .captcha-row{display:flex;align-items:center;gap:8px}.math-captcha .captcha-row .expression{font-weight:600;color:#333;white-space:nowrap}.math-captcha .captcha-row .answer-input{width:60px;padding:6px 8px;border:1px solid #ccc;border-radius:4px;text-align:center;font-size:inherit}.math-captcha .captcha-row .answer-input:focus{outline:none;border-color:#4caf50}.slider-captcha{display:flex;flex-direction:column;gap:1rem}.slider-captcha .slider-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;gap:1rem}.slider-captcha .slider-header .prompt{font-weight:600;color:#333;font-size:12px}.slider-captcha .slider-header .actions{display:flex;gap:1rem}.slider-captcha .slider-track{position:relative}.slider-captcha .slider-track .target-indicator{position:absolute;top:-10px;width:2px;height:20px;background:#ff5722;border-radius:1px;z-index:2}.slider-captcha .slider-track .slider{width:100%;height:6px;border-radius:3px;background:#ddd;outline:none;cursor:pointer}.slider-captcha .slider-track .slider::-webkit-slider-thumb{appearance:none;width:16px;height:16px;border-radius:50%;background:#4caf50;cursor:pointer}.pattern-captcha .pattern-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;gap:8px}.pattern-captcha .pattern-header .prompt{font-weight:600;color:#333;font-size:12px;white-space:nowrap}.pattern-captcha .pattern-header .pattern-sequence{display:flex;gap:4px;flex:1;justify-content:center}.pattern-captcha .pattern-header .pattern-sequence .pattern-item{width:20px;height:20px;background:#4caf50;color:#fff;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700}.pattern-captcha .pattern-header .actions{display:flex;gap:4px}.pattern-captcha .pattern-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:6px;margin-bottom:8px}.pattern-captcha .pattern-grid .pattern-button{aspect-ratio:1;border:1px solid #ddd;background:#fff;border-radius:4px;font-size:12px;font-weight:700;cursor:pointer;transition:all .2s ease}.pattern-captcha .pattern-grid .pattern-button:hover{background:#f0f0f0}.pattern-captcha .pattern-grid .pattern-button.selected{background:#4caf50;color:#fff;border-color:#4caf50}.pattern-captcha .pattern-grid .pattern-button:disabled{opacity:.6;cursor:not-allowed}.pattern-captcha .clear-btn{width:100%;padding:4px;background:#ff5722;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:11px}.pattern-captcha .clear-btn:hover{background:#e64a19}.verify-btn,.refresh-btn{width:28px;height:28px;border:none;border-radius:4px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:12px;transition:all .2s ease}:is(.verify-btn,.refresh-btn):disabled{opacity:.6;cursor:not-allowed}.verify-btn{background:#4caf50;color:#fff}.verify-btn:hover:not(:disabled){background:#45a049}.refresh-btn{background:#2196f3;color:#fff}.refresh-btn:hover:not(:disabled){background:#1976d2}.status-indicator{display:flex;align-items:center;gap:6px;margin-top:8px;padding:6px 8px;border-radius:4px;font-size:12px}.status-indicator .success{color:#4caf50}.status-indicator .error{color:#f44336}.status-indicator .status-text{font-weight:600}.status-indicator .attempts{color:#666;font-size:11px}.compact-captcha.small .verify-btn,.compact-captcha.small .refresh-btn{width:24px;height:24px;font-size:10px}.compact-captcha.small .answer-input{width:50px;padding:4px 6px}.compact-captcha.small .pattern-item{width:16px;height:16px;font-size:10px}@media (max-width: 480px){.compact-captcha{padding:8px}.compact-captcha .captcha-row{gap:6px}.compact-captcha .pattern-grid{gap:4px}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i2.RangeValueAccessor, selector: "input[type=range][formControlName],input[type=range][formControl],input[type=range][ngModel]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: NumericCaptchaComponent, decorators: [{
type: Component,
args: [{ selector: 'ngx-numeric-captcha', standalone: true, imports: [CommonModule, FormsModule], template: "<div class=\"compact-captcha\" [class.small]=\"size === 'small'\" [class.medium]=\"size === 'medium'\">\r\n \r\n <!-- Math CAPTCHA -->\r\n <div *ngIf=\"type === 'math'\" class=\"math-captcha\">\r\n <div class=\"captcha-row\">\r\n <span class=\"expression\">{{ mathExpression }} =</span>\r\n <input \r\n [(ngModel)]=\"userAnswer\" \r\n type=\"number\" \r\n placeholder=\"?\"\r\n class=\"answer-input\"\r\n [disabled]=\"isVerified !== null\"\r\n (keyup.enter)=\"verify()\"\r\n />\r\n <button class=\"verify-btn\" (click)=\"verify()\" [disabled]=\"isVerified === true\">\r\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\r\n <path d=\"M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z\"/>\r\n </svg>\r\n </button>\r\n <button class=\"refresh-btn\" (click)=\"refreshCaptcha()\" [disabled]=\"isVerified === true\">\r\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\r\n <path d=\"M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z\"/>\r\n </svg>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- Slider CAPTCHA -->\r\n <div *ngIf=\"type === 'slider'\" class=\"slider-captcha\">\r\n <div class=\"slider-header\">\r\n <span class=\"prompt\">Match position</span>\r\n <div class=\"actions\">\r\n <button class=\"verify-btn\" (click)=\"verify()\" [disabled]=\"isVerified === true\">\r\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\r\n <path d=\"M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z\"/>\r\n </svg>\r\n </button>\r\n <button class=\"refresh-btn\" (click)=\"refreshCaptcha()\" [disabled]=\"isVerified === true\">\r\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\r\n <path d=\"M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z\"/>\r\n </svg>\r\n </button>\r\n </div>\r\n </div>\r\n <div class=\"slider-track\">\r\n <div class=\"target-indicator\" [style.left.%]=\"targetPosition\"></div>\r\n <input \r\n type=\"range\" \r\n min=\"0\" \r\n max=\"100\" \r\n [(ngModel)]=\"sliderPosition\"\r\n class=\"slider\"\r\n />\r\n </div>\r\n </div>\r\n\r\n <!-- Pattern CAPTCHA -->\r\n <div *ngIf=\"type === 'pattern'\" class=\"pattern-captcha\">\r\n <div class=\"pattern-header\">\r\n <span class=\"prompt\">Repeat:</span>\r\n <div class=\"pattern-sequence\">\r\n <span *ngFor=\"let item of patternSequence\" class=\"pattern-item\">{{ item + 1 }}</span>\r\n </div>\r\n <div class=\"actions\">\r\n <button class=\"verify-btn\" (click)=\"verify()\" [disabled]=\"isVerified === true\">\r\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\r\n <path d=\"M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z\"/>\r\n </svg>\r\n </button>\r\n <button class=\"refresh-btn\" (click)=\"refreshCaptcha()\" [disabled]=\"isVerified === true\">\r\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\r\n <path d=\"M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z\"/>\r\n </svg>\r\n </button>\r\n </div>\r\n </div>\r\n <div class=\"pattern-grid\">\r\n <button \r\n *ngFor=\"let item of [].constructor(patternSize); let i = index\"\r\n class=\"pattern-button\"\r\n [class.selected]=\"userPattern.includes(i)\"\r\n (click)=\"addToPattern(i)\"\r\n [disabled]=\"userPattern.length >= patternSequence.length\"\r\n >\r\n {{ i + 1 }}\r\n </button>\r\n </div>\r\n <button class=\"clear-btn\" (click)=\"clearPattern()\" *ngIf=\"userPattern.length > 0\">Clear</button>\r\n </div>\r\n\r\n <!-- Status Indicator -->\r\n<div class=\"status-indicator\" *ngIf=\"isVerified !== null\">\r\n <svg *ngIf=\"isVerified\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"#4CAF50\">\r\n <path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z\"/>\r\n </svg>\r\n <svg *ngIf=\"!isVerified\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"#F44336\">\r\n <path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm3.5 6L12 10.5 8.5 8 7 9.5 10.5 12 7 14.5 8.5 16 12 13.5 15.5 16 17 14.5 13.5 12 17 9.5 15.5 8z\"/>\r\n </svg>\r\n <span class=\"status-text\">{{ isVerified ? 'Verified' : 'Try again' }}</span>\r\n <span class=\"attempts\" *ngIf=\"!isVerified && attempts < maxAttempts\">({{ attempts }}/{{ maxAttempts }})</span>\r\n <span class=\"attempts-max\" *ngIf=\"!isVerified && attempts >= maxAttempts\">Max attempts reached</span>\r\n</div>\r\n\r\n \r\n</div>\r\n", styles: [".compact-captcha{border:1px solid #ddd;border-radius:8px;padding:12px;background:#fff;font-family:Segoe UI,Tahoma,Geneva,Verdana,sans-serif;max-width:100%}.compact-captcha.small{padding:8px;font-size:14px}.compact-captcha.medium{padding:12px;font-size:15px}.math-captcha .captcha-row{display:flex;align-items:center;gap:8px}.math-captcha .captcha-row .expression{font-weight:600;color:#333;white-space:nowrap}.math-captcha .captcha-row .answer-input{width:60px;padding:6px 8px;border:1px solid #ccc;border-radius:4px;text-align:center;font-size:inherit}.math-captcha .captcha-row .answer-input:focus{outline:none;border-color:#4caf50}.slider-captcha{display:flex;flex-direction:column;gap:1rem}.slider-captcha .slider-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;gap:1rem}.slider-captcha .slider-header .prompt{font-weight:600;color:#333;font-size:12px}.slider-captcha .slider-header .actions{display:flex;gap:1rem}.slider-captcha .slider-track{position:relative}.slider-captcha .slider-track .target-indicator{position:absolute;top:-10px;width:2px;height:20px;background:#ff5722;border-radius:1px;z-index:2}.slider-captcha .slider-track .slider{width:100%;height:6px;border-radius:3px;background:#ddd;outline:none;cursor:pointer}.slider-captcha .slider-track .slider::-webkit-slider-thumb{appearance:none;width:16px;height:16px;border-radius:50%;background:#4caf50;cursor:pointer}.pattern-captcha .pattern-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;gap:8px}.pattern-captcha .pattern-header .prompt{font-weight:600;color:#333;font-size:12px;white-space:nowrap}.pattern-captcha .pattern-header .pattern-sequence{display:flex;gap:4px;flex:1;justify-content:center}.pattern-captcha .pattern-header .pattern-sequence .pattern-item{width:20px;height:20px;background:#4caf50;color:#fff;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700}.pattern-captcha .pattern-header .actions{display:flex;gap:4px}.pattern-captcha .pattern-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:6px;margin-bottom:8px}.pattern-captcha .pattern-grid .pattern-button{aspect-ratio:1;border:1px solid #ddd;background:#fff;border-radius:4px;font-size:12px;font-weight:700;cursor:pointer;transition:all .2s ease}.pattern-captcha .pattern-grid .pattern-button:hover{background:#f0f0f0}.pattern-captcha .pattern-grid .pattern-button.selected{background:#4caf50;color:#fff;border-color:#4caf50}.pattern-captcha .pattern-grid .pattern-button:disabled{opacity:.6;cursor:not-allowed}.pattern-captcha .clear-btn{width:100%;padding:4px;background:#ff5722;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:11px}.pattern-captcha .clear-btn:hover{background:#e64a19}.verify-btn,.refresh-btn{width:28px;height:28px;border:none;border-radius:4px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:12px;transition:all .2s ease}:is(.verify-btn,.refresh-btn):disabled{opacity:.6;cursor:not-allowed}.verify-btn{background:#4caf50;color:#fff}.verify-btn:hover:not(:disabled){background:#45a049}.refresh-btn{background:#2196f3;color:#fff}.refresh-btn:hover:not(:disabled){background:#1976d2}.status-indicator{display:flex;align-items:center;gap:6px;margin-top:8px;padding:6px 8px;border-radius:4px;font-size:12px}.status-indicator .success{color:#4caf50}.status-indicator .error{color:#f44336}.status-indicator .status-text{font-weight:600}.status-indicator .attempts{color:#666;font-size:11px}.compact-captcha.small .verify-btn,.compact-captcha.small .refresh-btn{width:24px;height:24px;font-size:10px}.compact-captcha.small .answer-input{width:50px;padding:4px 6px}.compact-captcha.small .pattern-item{width:16px;height:16px;font-size:10px}@media (max-width: 480px){.compact-captcha{padding:8px}.compact-captcha .captcha-row{gap:6px}.compact-captcha .pattern-grid{gap:4px}}\n"] }]
}], propDecorators: { type: [{
type: Input
}], size: [{
type: Input
}], captchaResult: [{
type: Output
}] } });
/**
* Generated bundle index. Do not edit.
*/
export { CaptchaType, NumericCaptchaComponent };
//# sourceMappingURL=ngx-numeric-captcha.mjs.map