-
- ${this.solved
- ? `✓ ${t('captcha.verified')}`
- : ``
- }
-
-
+
+
`
- if (!this.solved) {
- this.querySelector('.pow-captcha-btn').addEventListener('click', () => this.solve())
+ if (!this.solved && !this.solving) {
+ this.querySelector('.pow-captcha-label').addEventListener('click', () => this.solve())
}
}
async solve() {
- const btn = this.querySelector('.pow-captcha-btn')
- const progress = this.querySelector('.pow-captcha-progress')
- const progressBar = this.querySelector('.pow-captcha-progress-bar')
- const progressText = this.querySelector('.pow-captcha-progress-text')
+ if (this.solving || this.solved) return
- btn.disabled = true
- btn.textContent = t('captcha.solving')
- progress.style.display = 'flex'
+ this.solving = true
+ this.render()
try {
// Generate challenge (in production, fetch from server)
const { challenge, difficulty, timestamp, signature } = generateChallenge()
- // Solve with progress updates
- const result = await solveChallenge(challenge, difficulty, ({ attempts, elapsed }) => {
- const estimatedTotal = Math.pow(16, difficulty) / 2
- const percent = Math.min((attempts / estimatedTotal) * 100, 95)
- progressBar.style.width = `${percent}%`
- progressText.textContent = `${attempts.toLocaleString()} ${t('captcha.attempts')}`
- })
+ // Solve challenge
+ const result = await solveChallenge(challenge, difficulty)
// Store solution for form submission
this.solution = {
@@ -70,13 +67,14 @@ export class PowCaptcha extends HTMLElement {
timestamp
}
+ this.solving = false
this.solved = true
this.dispatchEvent(new CustomEvent('solved', { detail: this.solution }))
this.render()
} catch (error) {
- btn.disabled = false
- btn.textContent = t('captcha.error')
+ this.solving = false
+ this.render()
console.error('PoW Captcha error:', error)
}
}
@@ -105,52 +103,74 @@ customElements.define('pow-captcha', PowCaptcha)
const style = document.createElement('style')
style.textContent = `
.pow-captcha {
- padding: var(--space-md);
+ display: inline-block;
+ padding: var(--space-md) var(--space-lg);
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
- background: var(--color-surface);
+ background: var(--color-surface, var(--color-bg-secondary));
}
- .pow-captcha-btn {
- padding: var(--space-sm) var(--space-md);
- background: var(--color-primary);
- color: var(--color-bg);
- border: none;
- border-radius: var(--radius-sm);
+ .pow-captcha-label {
+ display: flex;
+ align-items: center;
+ gap: var(--space-md);
cursor: pointer;
+ user-select: none;
+ }
+
+ .pow-captcha--solving .pow-captcha-label,
+ .pow-captcha--solved .pow-captcha-label {
+ cursor: default;
+ }
+
+ .pow-captcha-checkbox {
+ width: 24px;
+ height: 24px;
+ border: 2px solid var(--color-border);
+ border-radius: var(--radius-sm);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: var(--color-bg);
+ transition: all 0.2s ease;
+ }
+
+ .pow-captcha:hover:not(.pow-captcha--solved):not(.pow-captcha--solving) .pow-captcha-checkbox {
+ border-color: var(--color-primary);
+ }
+
+ .pow-captcha--solved .pow-captcha-checkbox {
+ background: var(--color-success, #22c55e);
+ border-color: var(--color-success, #22c55e);
+ }
+
+ .pow-captcha-check {
+ width: 16px;
+ height: 16px;
+ color: white;
+ }
+
+ .pow-captcha-spinner {
+ width: 16px;
+ height: 16px;
+ border: 2px solid var(--color-border);
+ border-top-color: var(--color-primary);
+ border-radius: 50%;
+ animation: pow-spin 0.8s linear infinite;
+ }
+
+ @keyframes pow-spin {
+ to { transform: rotate(360deg); }
+ }
+
+ .pow-captcha-text {
font-size: var(--font-size-sm);
+ color: var(--color-text);
}
- .pow-captcha-btn:disabled {
- opacity: 0.6;
- cursor: wait;
- }
-
- .pow-captcha-success {
+ .pow-captcha--solved .pow-captcha-text {
color: var(--color-success, #22c55e);
font-weight: 500;
}
-
- .pow-captcha-progress {
- margin-top: var(--space-sm);
- align-items: center;
- gap: var(--space-sm);
- }
-
- .pow-captcha-progress-bar {
- height: 4px;
- background: var(--color-primary);
- border-radius: 2px;
- width: 0%;
- transition: width 0.1s;
- flex: 1;
- }
-
- .pow-captcha-progress-text {
- font-size: var(--font-size-xs);
- color: var(--color-text-muted);
- min-width: 100px;
- text-align: right;
- }
`
document.head.appendChild(style)