UNPKG

librecap

Version:

An open-source CAPTCHA Box alternative designed with privacy and data protection in mind.

653 lines (539 loc) 93.6 kB
/*! * LibreCap v0.3.5 * https://github.com/librecap/librecap * (c) 2025 LibreCap Contributors * Released under the Apache 2.0 License */ (function (global) { const POPUP_CSS = '.libre-captcha-popup{--libre-captcha-bg:#fff;--libre-captcha-border:#e0e0e0;--libre-captcha-text:#545454;--libre-captcha-box-shadow:0 4px 24px rgba(0,0,0,.25);--libre-captcha-selected:#0074bf;--libre-captcha-selected-dark:#2b87d3;--libre-captcha-font:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI","Inter",Roboto,"Noto Sans","Open Sans","Helvetica Neue",Ubuntu,Arial,sans-serif;background-color:var(--libre-captcha-bg);border:1px solid var(--libre-captcha-border);border-radius:12px;box-shadow:0 8px 32px rgba(0,0,0,.2);color:var(--libre-captcha-text);display:none;font-family:var(--libre-captcha-font);overflow:hidden;padding:20px;position:absolute;width:320px;z-index:1000}.libre-captcha-popup[data-theme=dark]{--libre-captcha-bg:#1e1e1e;--libre-captcha-border:#2d2d2d;--libre-captcha-text:#fff;--libre-captcha-box-shadow:0 4px 24px rgba(0,0,0,.4);--libre-captcha-selected:#2b87d3}.libre-captcha-popup[data-theme=dark] .challenge-button:before{background-color:hsla(0,0%,100%,.1)}.libre-captcha-popup[data-theme=dark] .challenge-button svg{fill:#fff;opacity:.7}.libre-captcha-popup[data-theme=dark] .challenge-button:hover svg{opacity:1}.libre-captcha-popup[data-theme=dark] .challenge-image{border:2px solid var(--captcha-border);box-shadow:0 2px 8px rgba(0,0,0,.3)}.libre-captcha-popup[data-theme=dark] .verify-button:disabled{background-color:#606060}.libre-captcha-popup[data-theme=dark] .verify-button:not(:disabled){background-color:#2b87d3;box-shadow:0 2px 4px rgba(0,0,0,.3)}.libre-captcha-popup[data-theme=dark] .verify-button:not(:disabled):hover{background-color:#3a96da;box-shadow:0 2px 6px rgba(0,0,0,.4)}.libre-captcha-popup[data-theme=dark] .back-button:before{background-color:hsla(0,0%,100%,.1)}.libre-captcha-popup[data-theme=dark] .github-button:hover{box-shadow:0 4px 8px rgba(0,0,0,.3)}.libre-captcha-popup[data-theme=dark] .info-link{color:#aaa}.libre-captcha-popup[data-theme=dark] .info-link:after{background:linear-gradient(to right,var(--libre-captcha-gradient-start,#4794e6),var(--libre-captcha-gradient-end,#2b87d3))}@media (prefers-color-scheme:dark){.libre-captcha-popup[data-theme=auto]{--libre-captcha-bg:#1e1e1e;--libre-captcha-border:#2d2d2d;--libre-captcha-text:#fff;--libre-captcha-box-shadow:0 4px 24px rgba(0,0,0,.4);--libre-captcha-selected:#2b87d3}.libre-captcha-popup[data-theme=auto] .challenge-button:before{background-color:hsla(0,0%,100%,.1)}.libre-captcha-popup[data-theme=auto] .challenge-image{border:2px solid var(--captcha-border);box-shadow:0 2px 8px rgba(0,0,0,.3)}.libre-captcha-popup[data-theme=auto] .verify-button:not(:disabled){background-color:#2b87d3;box-shadow:0 2px 4px rgba(0,0,0,.3)}.libre-captcha-popup[data-theme=auto] .verify-button:not(:disabled):hover{background-color:#3a96da;box-shadow:0 2px 6px rgba(0,0,0,.4)}.libre-captcha-popup[data-theme=auto] .verify-button:disabled{background-color:#606060}.libre-captcha-popup[data-theme=auto] .back-button:before{background-color:hsla(0,0%,100%,.1)}.libre-captcha-popup[data-theme=auto] .challenge-button svg{fill:#fff;opacity:.7}.libre-captcha-popup[data-theme=auto] .challenge-button:hover svg{opacity:1}.libre-captcha-popup[data-theme=auto] .challenge-image.selected:before{background-color:rgba(43,135,211,.3)}.libre-captcha-popup[data-theme=auto] .challenge-image.selected{border-color:var(--libre-captcha-selected);box-shadow:0 4px 16px rgba(0,116,191,.3)}.libre-captcha-popup[data-theme=auto] .challenge-image.selected:after{background-color:var(--libre-captcha-selected);box-shadow:0 2px 8px rgba(0,0,0,.4)}.libre-captcha-popup[data-theme=auto] .challenge-button:first-child:not(.loading):hover svg{fill:#a78bfa;transform:rotate(180deg)}.libre-captcha-popup[data-theme=auto] .challenge-button:nth-child(2):hover svg{fill:#56ccff}.libre-captcha-popup[data-theme=auto] .challenge-button:nth-child(3):hover svg{fill:#ffb74d}.libre-captcha-popup[data-theme=auto] .sound-challenge-view .challenge-button:nth-child(2):hover svg{fill:#66f296}.libre-captcha-popup[data-theme=auto] .info-link{color:#aaa}.libre-captcha-popup[data-theme=auto] .info-link:after{background:linear-gradient(to right,var(--libre-captcha-gradient-start,#4794e6),var(--libre-captcha-gradient-end,#2b87d3))}}@keyframes libreCaptchaFadeIn{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}@keyframes libreCaptchaPulse{0%{transform:scale(1)}50%{transform:scale(1.1)}to{transform:scale(1)}}.libre-captcha-popup.active{animation:libreCaptchaFadeIn .3s ease;display:block}.libre-captcha-popup .challenge-view{background-color:var(--libre-captcha-bg);display:flex;flex-direction:column;height:100%}.libre-captcha-popup .challenge-example{align-items:flex-start;border-bottom:1px solid var(--libre-captcha-border);display:flex;justify-content:space-between;margin-bottom:16px;padding-bottom:16px}.libre-captcha-popup .challenge-header{display:flex;flex:1;flex-direction:column;margin-bottom:0}.libre-captcha-popup .challenge-title{color:var(--captcha-text);font-size:15px;font-weight:500;letter-spacing:.01em;line-height:1.4;margin-bottom:12px;text-align:left}.libre-captcha-popup .example-image{border:1px solid var(--captcha-border);border-radius:4px;margin-left:12px;overflow:hidden}.libre-captcha-popup .example-image img{display:block;height:80px;object-fit:cover;width:80px}.libre-captcha-popup .challenge-controls{display:flex;gap:16px;justify-content:flex-start;margin-bottom:0}.libre-captcha-popup .challenge-button{background:none;border:none;border-radius:6px;cursor:pointer;overflow:hidden;padding:6px;position:relative;transition:transform .2s cubic-bezier(.4,0,.2,1);will-change:transform}@keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.libre-captcha-popup .challenge-button.loading{pointer-events:none}.libre-captcha-popup .challenge-button:before{background-color:rgba(0,0,0,.15);border-radius:6px;content:"";inset:0;opacity:0;position:absolute;transform:scale(1);transition:opacity .2s ease}.libre-captcha-popup .challenge-button:hover:before{opacity:1}.libre-captcha-popup .challenge-button.loading:before{opacity:1;transform:scale(1)}.libre-captcha-popup .challenge-button svg{height:20px;width:20px;fill:#545454;opacity:.7;position:relative;transition:all .2s cubic-bezier(.4,0,.2,1);z-index:1}.libre-captcha-popup .challenge-button:hover{transform:translateY(-2px) scale(1.05)}.libre-captcha-popup .challenge-button:hover svg{opacity:1;transform:scale(1.1)}.libre-captcha-popup .challenge-button:first-child:not(.loading):hover svg{fill:#8b5cf6;transform:rotate(180deg)}.libre-captcha-popup .challenge-button:nth-child(3):hover svg{fill:#ff9800}.libre-captcha-popup .challenge-button.loading svg{animation:spin 1s linear infinite;opacity:1;transform-origin:center}.libre-captcha-popup .challenge-button:first-child.loading svg{fill:#8b5cf6}.libre-captcha-popup .challenge-button:nth-child(2).loading svg{fill:#00b8d4}.libre-captcha-popup .challenge-grid{display:grid;flex:1;gap:12px;grid-template-columns:repeat(3,1fr);margin-bottom:10px}.libre-captcha-popup .challenge-image{aspect-ratio:1;border:2px solid transparent;border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,.15);cursor:pointer;overflow:hidden;position:relative;transition:all .2s ease}.libre-captcha-popup .challenge-image img{display:block;height:100%;object-fit:cover;width:100%}.libre-captcha-popup .challenge-image.selected{border-color:var(--libre-captcha-selected);box-shadow:0 4px 16px rgba(0,116,191,.3)}.libre-captcha-popup .challenge-image.selected:after{background-color:var(--libre-captcha-selected);background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' fill=\'%23fff\' viewBox=\'0 0 24 24\'%3E%3Cpath d=\'M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z\'/%3E%3C/svg%3E");background-position:50%;background-repeat:no-repeat;background-size:18px;border-radius:50%;box-shadow:0 2px 8px rgba(0,0,0,.3);content:"";height:26px;position:absolute;right:6px;top:6px;width:26px}.libre-captcha-popup .challenge-image.selected:before{background-color:rgba(0,116,191,.25);content:"";height:100%;left:0;pointer-events:none;position:absolute;top:0;width:100%;z-index:1}.libre-captcha-popup .verify-button{background-color:#0074bf;border:none;border-radius:6px;box-shadow:0 4px 12px rgba(0,0,0,.15);color:#fff;cursor:pointer;font-size:16px;font-weight:500;letter-spacing:.02em;margin-top:8px;opacity:1;outline:1px solid rgba(0,0,0,.1);padding:14px;text-shadow:0 1px 2px rgba(0,0,0,.2);transition:background-color .2s ease,box-shadow .2s ease;width:100%}.libre-captcha-popup .verify-button:disabled{background-color:#a0a0a0;color:hsla(0,0%,100%,.9);cursor:not-allowed}.libre-captcha-popup .verify-button:not(:disabled):hover{background-color:#0066a8;box-shadow:0 2px 6px rgba(0,0,0,.2)}.libre-captcha-popup .sound-challenge-container{display:flex;flex-direction:column;gap:16px;margin-bottom:16px}.libre-captcha-popup .audio-container{align-items:center;background-color:var(--libre-captcha-grid-bg);border:1px solid var(--libre-captcha-border);border-radius:8px;display:flex;justify-content:center;padding:20px}.libre-captcha-popup .audio-player{height:40px;max-width:320px;width:100%}.libre-captcha-popup .sound-input-container{margin-bottom:0;width:100%}.libre-captcha-popup .sound-input-field{background-color:var(--libre-captcha-bg);border:1px solid var(--libre-captcha-border);border-radius:6px;box-sizing:border-box;color:var(--libre-captcha-text);font-size:15px;font-weight:400;letter-spacing:.01em;outline:none;padding:12px 16px;transition:border-color .2s ease,box-shadow .2s ease;width:100%}.libre-captcha-popup .sound-input-field:focus{border-color:#0074bf;box-shadow:0 0 0 2px rgba(0,116,191,.2)}.libre-captcha-popup[data-theme=dark] .sound-input-field{background-color:#2a2a2a;border-color:#444;color:#eee}.libre-captcha-popup[data-theme=dark] .sound-input-field:focus{border-color:#08d;box-shadow:0 0 0 2px rgba(0,136,221,.2)}@media (prefers-color-scheme:dark){.libre-captcha-popup[data-theme=auto] .sound-input-field{background-color:#2a2a2a;border-color:#444;color:#eee}.libre-captcha-popup[data-theme=auto] .sound-input-field:focus{border-color:#08d;box-shadow:0 0 0 2px rgba(0,136,221,.2)}}.libre-captcha-popup .info-content{background-color:var(--libre-captcha-bg);display:none;flex-direction:column;height:100%;min-height:400px;width:100%}.libre-captcha-popup .info-header{align-items:center;display:flex;margin-bottom:32px;position:relative}.libre-captcha-popup .back-button{align-items:center;background:none;border:none;border-radius:6px;color:var(--libre-captcha-text);cursor:pointer;display:flex;font-size:15px;font-weight:500;gap:8px;letter-spacing:.01em;padding:8px 16px;position:relative;transition:background-color .2s ease,color .2s ease}.libre-captcha-popup .back-button svg{height:20px;width:20px;fill:var(--libre-captcha-text);transition:transform .3s ease}.libre-captcha-popup .back-button:hover{background-color:rgba(0,0,0,.15)}.libre-captcha-popup .back-button:hover svg{transform:translateX(-4px)}.libre-captcha-popup[data-theme=dark] .back-button:hover{background-color:hsla(0,0%,100%,.1)}.libre-captcha-popup .info-main-content{align-items:center;display:flex;flex-direction:column;gap:24px;padding:24px;text-align:center}.libre-captcha-popup .info-logo-container{align-items:center;display:flex;gap:4px;margin-bottom:12px}.libre-captcha-popup .info-logo{border-radius:8px;height:40px;transition:transform .3s ease;width:40px}.libre-captcha-popup .info-brand-title{color:var(--libre-captcha-text);font-size:22px;font-weight:700;letter-spacing:-.01em;transition:transform .3s ease}.libre-captcha-popup .info-text{color:var(--libre-captcha-text);font-size:15px;font-weight:400;letter-spacing:.01em;line-height:1.5;margin-bottom:8px;opacity:.9;padding:0 16px}.libre-captcha-popup .github-container{margin:8px 0 24px}.libre-captcha-popup .github-button{align-items:center;background-color:var(--libre-captcha-bg);border:1px solid var(--libre-captcha-border);border-radius:6px;color:var(--libre-captcha-text);display:inline-flex;font-size:15px;font-weight:500;gap:8px;letter-spacing:.01em;padding:8px 16px;text-decoration:none;transition:background-color .3s ease,box-shadow .3s ease,transform .2s ease}.libre-captcha-popup .github-button:hover{background-color:var(--libre-captcha-selected);box-shadow:0 4px 12px rgba(0,0,0,.2);color:#fff;transform:translateY(-2px)}.libre-captcha-popup .info-links{border-top:1px solid var(--libre-captcha-border);display:flex;gap:24px;justify-content:center;padding-top:16px;width:100%}.libre-captcha-popup .info-link{color:var(--libre-captcha-text);font-size:15px;font-weight:500;letter-spacing:.01em;opacity:.8;padding:2px 0;position:relative;text-decoration:none;transition:all .2s ease}.libre-captcha-popup .info-link:hover{opacity:1;transform:translateY(-1px)}.libre-captcha-popup .info-link:after{background:linear-gradient(to right,var(--libre-captcha-gradient-start,#3b82f6),var(--libre-captcha-gradient-end,#2563eb));bottom:0;content:"";height:1px;left:0;position:absolute;transition:width .3s ease;width:0}.libre-captcha-popup .info-link:hover:after{width:100%}.libre-captcha-popup[data-theme=dark] .back-button svg,.libre-captcha-popup[data-theme=dark] .github-button svg{fill:#fff}@media (prefers-color-scheme:dark){.libre-captcha-popup[data-theme=auto] .back-button svg,.libre-captcha-popup[data-theme=auto] .github-button svg{fill:#fff}}.libre-captcha-popup[data-theme=dark] .challenge-image.selected{border-color:var(--libre-captcha-selected-dark);box-shadow:0 4px 16px rgba(43,135,211,.3)}.libre-captcha-popup[data-theme=dark] .challenge-image.selected:after{background-color:var(--libre-captcha-selected-dark);box-shadow:0 2px 8px rgba(0,0,0,.4)}.libre-captcha-popup .brand-wrapper{align-items:center;display:flex;text-decoration:none;transition:opacity .2s ease}.libre-captcha-popup .brand-wrapper:has(.captcha-logo):has(.captcha-title),.libre-captcha-popup .brand-wrapper:has(.info-logo):has(.info-brand-title){gap:16px}.libre-captcha-popup .brand-wrapper:hover{opacity:.8}.libre-captcha-popup .brand-wrapper:not(a){opacity:1}.libre-captcha-popup .brand-wrapper:hover .info-logo{transform:rotate(5deg) scale(1.03)}.libre-captcha-popup .brand-wrapper:hover .info-brand-title{transform:translateX(4px)}.libre-captcha-popup .challenge-progress{background-color:hsla(0,0%,50%,.2);height:4px;left:0;overflow:hidden;position:absolute;top:0;width:100%}.libre-captcha-popup .challenge-progress-bar{background-color:var(--libre-captcha-selected);height:100%;left:0;position:absolute;top:0;transition:width .3s ease}.libre-captcha-popup .challenge-progress-text{background-color:hsla(0,0%,50%,.1);border-radius:12px;color:var(--libre-captcha-text);font-size:12px;opacity:.7;padding:4px 8px;position:absolute;right:8px;top:8px}.libre-captcha-popup[data-theme=dark] .challenge-progress-bar{background-color:var(--libre-captcha-selected-dark)}@media (prefers-color-scheme:dark){.libre-captcha-popup[data-theme=auto] .challenge-progress-bar{background-color:var(--libre-captcha-selected-dark)}}.libre-captcha-popup .progress-overlay{align-items:center;background-color:var(--libre-captcha-bg);border-radius:12px;display:flex;flex-direction:column;inset:0;opacity:0;padding:20px;pointer-events:none;position:absolute;transition:opacity .3s ease;z-index:1000}.libre-captcha-popup .progress-overlay.active{opacity:1;pointer-events:all}.libre-captcha-popup .progress-overlay-content{align-items:center;display:flex;flex:1;flex-direction:column;justify-content:center;width:80%}.libre-captcha-popup .progress-overlay-bar{background-color:var(--libre-captcha-border);border-radius:12px;height:12px;overflow:hidden;position:relative;width:80%}.libre-captcha-popup .progress-overlay-fill{background-color:var(--libre-captcha-text);height:100%;left:0;position:absolute;top:0;transition:width .8s cubic-bezier(.4,0,.2,1);width:0}.libre-captcha-popup .progress-overlay-text{color:var(--libre-captcha-text);font-size:20px;font-weight:600;margin-bottom:24px;opacity:.9}.libre-captcha-popup[data-theme=dark] .progress-overlay{background-color:var(--libre-captcha-bg);border:1px solid var(--libre-captcha-border)}.libre-captcha-popup[data-theme=dark] .progress-overlay-bar{background-color:var(--libre-captcha-border)}.libre-captcha-popup[data-theme=dark] .progress-overlay-fill{background-color:var(--libre-captcha-text);opacity:.9}@media (prefers-color-scheme:dark){.libre-captcha-popup[data-theme=auto] .progress-overlay{background-color:var(--libre-captcha-bg);border:1px solid var(--libre-captcha-border)}.libre-captcha-popup[data-theme=auto] .progress-overlay-bar{background-color:var(--libre-captcha-border)}.libre-captcha-popup[data-theme=auto] .progress-overlay-fill{background-color:var(--libre-captcha-text);opacity:.9}}.libre-captcha-popup .progress-overlay-brand{align-items:center;display:flex;gap:12px;margin-top:auto}.libre-captcha-popup .progress-overlay-brand img{border-radius:6px;height:32px;width:32px}.libre-captcha-popup .progress-overlay-brand-title{color:var(--libre-captcha-text);font-size:16px;font-weight:600}.libre-captcha-popup .sound-challenge-view .challenge-button:nth-child(2):hover svg{fill:#4caf50}.libre-captcha-popup[data-theme=dark] .challenge-button:first-child:not(.loading):hover svg{fill:#a78bfa;transform:rotate(180deg)}.libre-captcha-popup[data-theme=dark] .challenge-button:nth-child(3):hover svg{fill:#ffb74d}.libre-captcha-popup[data-theme=dark] .sound-challenge-view .challenge-button:nth-child(2):hover svg{fill:#66f296}.libre-captcha-popup[data-theme=dark] .github-button{background-color:var(--libre-captcha-bg);border-color:var(--libre-captcha-border);box-shadow:0 2px 5px rgba(0,0,0,.2);color:var(--libre-captcha-text)}.libre-captcha-popup[data-theme=dark] .github-button:hover{background-color:var(--libre-captcha-selected);box-shadow:0 4px 12px rgba(0,0,0,.3);color:#fff}@media (prefers-color-scheme:dark){.libre-captcha-popup[data-theme=auto] .github-button{background-color:var(--libre-captcha-bg);border-color:var(--libre-captcha-border);box-shadow:0 2px 5px rgba(0,0,0,.2);color:var(--libre-captcha-text)}.libre-captcha-popup[data-theme=auto] .github-button:hover{background-color:var(--libre-captcha-selected);box-shadow:0 4px 12px rgba(0,0,0,.3);color:#fff}}.libre-captcha-popup .challenge-button:nth-child(2):hover svg{fill:#00b8d4;animation:libreCaptchaPulse 1s ease-in-out infinite}.libre-captcha-popup[data-theme=dark] .challenge-button:nth-child(2):hover svg{fill:#56ccff;animation:libreCaptchaPulse 1s ease-in-out infinite}@media (prefers-color-scheme:dark){.libre-captcha-popup[data-theme=auto] .challenge-button:nth-child(2):hover svg{fill:#56ccff;animation:libreCaptchaPulse 1s ease-in-out infinite}}'; const WIDGET_CSS = '.libre-captcha-widget{--libre-captcha-bg:#fff;--libre-captcha-border:#e0e0e0;--libre-captcha-text:#404040;--libre-captcha-checkbox-border:#c5c5c5;--libre-captcha-checkbox-checked:#2563eb;--libre-captcha-brand-color:#111827;--libre-captcha-link-hover:#4b5563;--libre-captcha-spinner-color:#6b7280;--libre-captcha-gradient-start:#3b82f6;--libre-captcha-gradient-end:#2563eb;align-items:center;background-color:var(--libre-captcha-bg);border:1px solid var(--libre-captcha-border);border-radius:8px;box-shadow:0 2px 6px rgba(0,0,0,.05);box-sizing:border-box;display:flex;font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Inter,Roboto,Noto Sans,Open Sans,Helvetica Neue,Ubuntu,Arial,sans-serif;height:74px;justify-content:space-between;padding:0 16px;position:relative;transition:all .3s ease;width:300px}.libre-captcha-widget[data-theme=dark]{--libre-captcha-bg:#1a1a1a;--libre-captcha-border:#303030;--libre-captcha-text:#e5e7eb;--libre-captcha-checkbox-border:#4b5563;--libre-captcha-checkbox-checked:#3b82f6;--libre-captcha-brand-color:#f3f4f6;--libre-captcha-link-hover:#e5e7eb;--libre-captcha-spinner-color:#9ca3af;--libre-captcha-gradient-start:#4f96ff;--libre-captcha-gradient-end:#3b82f6;box-shadow:0 2px 6px rgba(0,0,0,.2)}@media (prefers-color-scheme:dark){.libre-captcha-widget[data-theme=auto]{--libre-captcha-bg:#1a1a1a;--libre-captcha-border:#303030;--libre-captcha-text:#e5e7eb;--libre-captcha-checkbox-border:#4b5563;--libre-captcha-checkbox-checked:#3b82f6;--libre-captcha-brand-color:#f3f4f6;--libre-captcha-link-hover:#e5e7eb;--libre-captcha-spinner-color:#9ca3af;--libre-captcha-gradient-start:#4f96ff;--libre-captcha-gradient-end:#3b82f6;box-shadow:0 2px 6px rgba(0,0,0,.2)}}@keyframes libreCaptchaSpin{to{transform:translate(-50%,-50%) rotate(1turn)}}@keyframes libreCaptchaFadeIn{0%{opacity:0}to{opacity:1}}.libre-captcha-widget,.libre-captcha-widget *{transition:background-color .3s ease,border-color .3s ease,color .3s ease,box-shadow .3s ease,transform .3s ease}.libre-captcha-widget:hover{box-shadow:0 4px 12px rgba(0,0,0,.08);transform:translateY(-1px)}.libre-captcha-widget .checkbox-container{align-items:center;display:flex;gap:14px}.libre-captcha-widget .checkbox-wrapper{align-items:center;display:flex;height:24px;justify-content:center;position:relative;width:24px}.libre-captcha-widget .captcha-checkbox{appearance:none;-webkit-appearance:none;background-color:transparent;border:2px solid var(--libre-captcha-checkbox-border);border-radius:4px;box-sizing:border-box;cursor:pointer;height:24px;left:0;margin:0;padding:0;position:absolute;top:0;transition:all .25s ease;width:24px}.libre-captcha-widget .captcha-checkbox:hover{border-color:var(--libre-captcha-checkbox-checked);box-shadow:0 0 0 1px rgba(37,99,235,.1)}.libre-captcha-widget .captcha-checkbox:checked{background-color:var(--libre-captcha-checkbox-checked);border-color:var(--libre-captcha-checkbox-checked);box-shadow:0 0 0 1px rgba(37,99,235,.2)}.libre-captcha-widget .captcha-checkbox:checked:after{border:solid #fff;border-width:0 2px 2px 0;content:"";height:11px;left:50%;position:absolute;top:45%;transform:translate(-50%,-50%) rotate(45deg);width:6px}.libre-captcha-widget.loading .captcha-checkbox{opacity:0}.libre-captcha-widget .loading-spinner{display:none;height:24px;left:0;position:absolute;top:0;width:24px}.libre-captcha-widget .loading-spinner:after{animation:libreCaptchaSpin .8s cubic-bezier(.4,0,.2,1) infinite;border:3px solid hsla(220,9%,46%,.2);border-radius:50%;border-top:3px solid var(--libre-captcha-spinner-color);box-sizing:border-box;content:"";height:28px;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);width:28px}.libre-captcha-widget.loading .loading-spinner{display:block}.libre-captcha-widget .captcha-label{color:var(--libre-captcha-text);font-size:14px;font-weight:400;letter-spacing:.01em;user-select:none}.libre-captcha-widget .branding-container{align-items:center;box-sizing:border-box;display:flex;flex-direction:column;height:calc(100% - 14px);justify-content:center;margin-right:-8px;max-height:64px;padding:5px 10px}.libre-captcha-widget .brand-link{align-items:center;display:flex;flex-direction:column;text-decoration:none;transition:all .25s ease}.libre-captcha-widget .brand-link:hover{opacity:1;transform:scale(1.03)}.libre-captcha-widget .captcha-logo{filter:drop-shadow(0 2px 2px rgba(0,0,0,.1));height:auto;margin-bottom:2px;transition:transform .3s ease;width:32px}.libre-captcha-widget .brand-link:hover .captcha-logo{transform:rotate(5deg)}.libre-captcha-widget .captcha-title{background:linear-gradient(to right,var(--libre-captcha-gradient-start),var(--libre-captcha-gradient-end));-webkit-background-clip:text;color:var(--libre-captcha-brand-color);font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Inter,Roboto,sans-serif;font-size:10px;font-weight:700;letter-spacing:.02em;margin-bottom:2px;text-align:center;-webkit-text-fill-color:transparent;background-clip:text}html[data-darkreader-mode] .libre-captcha-widget .captcha-title{background-clip:text!important;-webkit-background-clip:text!important;color:transparent!important;-webkit-text-fill-color:transparent!important}.libre-captcha-widget .links-container{display:flex;font-size:9px;gap:8px;justify-content:center}.libre-captcha-widget .links-container a{color:var(--libre-captcha-text);opacity:.8;padding:2px 0;position:relative;text-decoration:none;transition:all .2s ease}.libre-captcha-widget .links-container a:hover{color:var(--libre-captcha-link-hover);opacity:1;transform:translateY(-1px)}.libre-captcha-widget .links-container a:after{background:linear-gradient(to right,var(--libre-captcha-gradient-start),var(--libre-captcha-gradient-end));bottom:0;content:"";height:1px;left:0;position:absolute;transition:width .3s ease;width:0}.libre-captcha-widget .links-container a:hover:after{width:100%}@media screen and (max-width:350px){.libre-captcha-widget{gap:6px;height:68px;margin-left:8px;margin-right:8px;min-width:200px;padding:0 10px;width:calc(100% - 16px)}.libre-captcha-widget .checkbox-container{flex-shrink:0;gap:8px;margin-right:4px}.libre-captcha-widget .branding-container{flex-shrink:1;height:calc(100% - 10px);max-height:58px;max-width:calc(100% - 100px);min-width:0;padding:4px 6px}.libre-captcha-widget .captcha-label{font-size:13px;white-space:nowrap}.libre-captcha-widget .captcha-logo{margin-bottom:1px;width:26px}.libre-captcha-widget .captcha-title{font-size:9px;margin-bottom:1px}.libre-captcha-widget .links-container{font-size:8px;gap:4px}}.libre-captcha-widget .error-section{align-items:center;animation:libreCaptchaFadeIn .3s ease;background-color:var(--libre-captcha-bg);border-radius:8px;bottom:0;display:none;justify-content:center;left:0;padding:12px 16px;position:absolute;right:0;text-align:center;top:0;z-index:10}.libre-captcha-widget .error-section.active{display:flex}.libre-captcha-widget .error-message{color:#dc2626;font-size:13px;font-weight:500;line-height:1.4}@media screen and (max-width:350px){.libre-captcha-widget .error-message{font-size:12px}}'; function injectCSS(...cssStrings) { const style = document.createElement('style'); style.innerHTML = cssStrings.join('\n'); document.head.appendChild(style); } class UI { constructor() { this.selectedImages = new Set(); this.activePopup = null; this.activeOverlay = null; this.activeInfoPage = null; this.currentView = 'challenge'; this.previousView = null; this.currentImagePowChallenge = null; this.currentImageChallenge = null; this.currentImageChallengeIndex = 0; this.totalImageChallenges = 1; this.selectedImagesPerChallenge = []; this.currentAudioPowChallenge = null; this.currentAudioChallenge = null; this.currentAudioChallengeIndex = 0; this.totalAudioChallenges = 1; this.currentAudioInput = ''; this._resizeHandler = null; this.imageChallenge = new ImageChallenge(this); this.audioChallenge = new AudioChallenge(this); this.solve_pow_challenge = solve_pow_challenge; this.initialRequest = initialRequest; this.challengeRequest = challengeRequest; this.audioChallengeRequest = audioChallengeRequest; } createImageElement(imageBytes, onLoadCallback = null) { const blob = new Blob([imageBytes], { type: 'image/webp' }); const img = document.createElement('img'); const url = URL.createObjectURL(blob); img.src = url; img.onload = () => { URL.revokeObjectURL(url); if (onLoadCallback) onLoadCallback(); }; return img; } updateVerifyButton(button, challengeIndex) { if (!button) return; const isLastChallenge = challengeIndex === this.totalImageChallenges - 1; button.disabled = this.selectedImagesPerChallenge[challengeIndex]?.size === 0; button.textContent = isLastChallenge ? 'VERIFY' : 'NEXT'; } cleanup() { if (this._resizeHandler) { window.removeEventListener('resize', this._resizeHandler); this._resizeHandler = null; } if (this.activePopup) { this.pauseAudioIfPlaying(this.activePopup); const audioPlayer = this.activePopup.querySelector('.audio-player'); if (audioPlayer && audioPlayer.src && audioPlayer.src.startsWith('blob:')) { URL.revokeObjectURL(audioPlayer.src); } this.activePopup.remove(); this.activePopup = null; } } showError(widgetElement, errorMessage) { const errorSection = widgetElement.querySelector('.error-section'); const errorMessageElement = errorSection.querySelector('.error-message'); errorMessageElement.textContent = errorMessage; errorSection.classList.add('active'); setTimeout(() => { errorSection.classList.remove('active'); }, 5000); } async newImageChallenge(container, config) { try { const powChallenges = await initialRequest(config.apiEndpoint, config.siteKey); const powSolution = await solve_pow_challenge(powChallenges.first); const imageChallenge = await challengeRequest( config.apiEndpoint, config.siteKey, powChallenges.first, powSolution ); this.currentImagePowChallenge = powChallenges.second; this.currentImageChallenge = imageChallenge; this.selectedImages.clear(); this.currentImageChallengeIndex = 0; this.totalImageChallenges = Math.floor((imageChallenge.images.length - 1) / 9); this.selectedImagesPerChallenge = Array(this.totalImageChallenges) .fill() .map(() => new Set()); if (this.activePopup) { this.updateExistingPopup(imageChallenge); } else { this.createChallengePopup(container, imageChallenge, config); } } catch (error) { console.error('Challenge error:', error); this.showError(container, error.message || 'Failed to load challenge'); container.classList.remove('loading'); } } async newAudioChallenge(container, config) { try { const powChallenges = await initialRequest(config.apiEndpoint, config.siteKey); const powSolution = await solve_pow_challenge(powChallenges.first); const audioChallenge = await audioChallengeRequest( config.apiEndpoint, config.language, config.siteKey, powChallenges.first, powSolution ); this.currentAudioPowChallenge = powChallenges.second; this.currentAudioChallenge = audioChallenge; this.currentAudioChallengeIndex = 0; this.totalAudioChallenges = audioChallenge.audios.length; this.currentAudioInput = ''; if (this.activePopup) { if (!this.activePopup.querySelector('.sound-challenge-view')) { const soundChallengeView = this.audioChallenge.createSoundChallengeView( this.activePopup ); soundChallengeView.classList.add('sound-challenge-view'); this.activePopup.appendChild(soundChallengeView); } this.updateExistingAudioPopup(audioChallenge); } else { this.createChallengePopup(container, audioChallenge, config); } } catch (error) { console.error('Audio challenge error:', error); this.showError(container, error.message || 'Failed to load audio challenge'); container.classList.remove('loading'); } } updateExistingPopup(imageChallenge) { const progressIndicator = this.activePopup.querySelector('.challenge-progress'); if (progressIndicator) { progressIndicator.textContent = `Challenge ${this.currentImageChallengeIndex + 1} of ${this.totalImageChallenges}`; } const exampleImage = this.activePopup.querySelector('.example-image img'); if (exampleImage) { const img = this.createImageElement(imageChallenge.images[0]); exampleImage.parentNode.replaceChild(img, exampleImage); } const grid = this.activePopup.querySelector('.challenge-grid'); const verifyButton = this.activePopup.querySelector('.verify-button'); if (grid && verifyButton) { this.updateGrid(grid, imageChallenge, 0, verifyButton); } if (this.currentView === 'info') { this.showChallengeView(this.activePopup); } } updateExistingAudioPopup(audioChallenge) { const audioPlayer = this.activePopup.querySelector('.audio-player'); if (audioPlayer) { if (audioPlayer.src && audioPlayer.src.startsWith('blob:')) { URL.revokeObjectURL(audioPlayer.src); } const blob = new Blob([audioChallenge.audios[this.currentAudioChallengeIndex]], { type: 'audio/mp3', }); const url = URL.createObjectURL(blob); audioPlayer.src = url; audioPlayer.addEventListener( 'error', () => { console.error('Error loading audio'); URL.revokeObjectURL(url); }, { once: true } ); } const progressIndicator = this.activePopup.querySelector('.challenge-progress'); if (progressIndicator) { progressIndicator.textContent = `Challenge ${this.currentAudioChallengeIndex + 1} of ${this.totalAudioChallenges}`; } const verifyButton = this.activePopup.querySelector('.verify-button'); if (verifyButton) { verifyButton.disabled = true; verifyButton.textContent = this.currentAudioChallengeIndex < this.totalAudioChallenges - 1 ? 'NEXT' : 'VERIFY'; } const inputField = this.activePopup.querySelector('.sound-input-field'); if (inputField) { inputField.value = this.currentAudioInput; } if (this.currentView === 'info') { this.showChallengeView(this.activePopup); } } updateGrid(grid, imageChallenge, challengeIndex, verifyButton) { grid.innerHTML = ''; const startIndex = challengeIndex * 9; this.selectedImages = this.selectedImagesPerChallenge[challengeIndex]; if (!this.selectedImagesPerChallenge[challengeIndex]) { this.selectedImagesPerChallenge[challengeIndex] = new Set(); } for (let i = startIndex; i < startIndex + 9; i++) { const imageContainer = document.createElement('div'); imageContainer.className = 'challenge-image'; if (this.selectedImagesPerChallenge[challengeIndex].has(i - startIndex)) { imageContainer.classList.add('selected'); } const img = this.createImageElement(imageChallenge.images[i + 1]); imageContainer.appendChild(img); imageContainer.addEventListener('click', () => { const wasSelected = imageContainer.classList.contains('selected'); imageContainer.classList.toggle('selected'); if (!wasSelected) { this.selectedImagesPerChallenge[challengeIndex].add(i - startIndex); } else { this.selectedImagesPerChallenge[challengeIndex].delete(i - startIndex); } this.updateVerifyButton(verifyButton, challengeIndex); }); grid.appendChild(imageContainer); } this.updateVerifyButton(verifyButton, challengeIndex); const progressIndicator = this.activePopup.querySelector('.challenge-progress'); if (progressIndicator) { progressIndicator.textContent = `Challenge ${challengeIndex + 1} of ${this.totalImageChallenges}`; } } createSVGIcons() { return { reload: `<svg viewBox="0 0 24 24"><path d="M17.65 6.35A7.958 7.958 0 0012 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.08A5.99 5.99 0 0112 18c-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"/></svg>`, sound: `<svg viewBox="0 0 24 24"><path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/></svg>`, image: `<svg viewBox="0 0 24 24"><path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/></svg>`, info: `<svg viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg>`, close: `<svg viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>`, back: `<svg viewBox="0 0 24 24"><path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg>`, }; } createChallengePopup(container, challenge, config) { const icons = this.createSVGIcons(); this.cleanup(); const popup = document.createElement('div'); popup.className = 'libre-captcha-popup'; popup.style.visibility = 'hidden'; popup.config = config; popup.triggerContainer = container; const theme = container.getAttribute('data-theme') || 'auto'; popup.setAttribute('data-theme', theme); this.activePopup = popup; const isAudioChallenge = challenge.audios && challenge.audios.length > 0; const isImageChallenge = challenge.images && challenge.images.length > 0; const challengeView = document.createElement('div'); challengeView.className = 'challenge-view'; challengeView.setAttribute('data-theme', theme); if (isImageChallenge) { const exampleSection = document.createElement('div'); exampleSection.className = 'challenge-example'; const header = document.createElement('div'); header.className = 'challenge-header'; const title = document.createElement('div'); title.className = 'challenge-title'; title.innerText = 'Select all images containing a dog with the same facial expression'; const controls = document.createElement('div'); controls.className = 'challenge-controls'; const buttons = [ { icon: icons.reload, title: 'New challenge', action: () => { const button = controls.children[0]; button.classList.add('loading'); const overlay = this.activePopup.querySelector('.progress-overlay'); const overlayText = overlay.querySelector('.progress-overlay-text'); const overlayFill = overlay.querySelector('.progress-overlay-fill'); overlayText.textContent = '0 done'; overlayFill.style.width = '0%'; this.imageChallenge.newImageChallenge(container, config).finally(() => { button.classList.remove('loading'); }); }, }, { icon: icons.sound, title: 'Sound challenge', action: () => { const button = controls.children[1]; button.classList.add('loading'); if ( this.audioChallenge.currentAudioChallenge && this.audioChallenge.currentAudioChallenge.audios ) { button.classList.remove('loading'); this.showSoundChallengeView(popup); } else { this.audioChallenge.newAudioChallenge(container, config).finally(() => { button.classList.remove('loading'); this.showSoundChallengeView(popup); }); } }, }, { icon: icons.info, title: 'Information', action: () => this.showInfoView(popup) }, ]; buttons.forEach(({ icon, title, action }) => { const button = document.createElement('button'); button.className = 'challenge-button'; button.title = title; button.innerHTML = icon; if (action) { button.addEventListener('click', action); } controls.appendChild(button); }); const exampleImage = document.createElement('div'); exampleImage.className = 'example-image'; const exampleImg = this.imageChallenge.createImageElement(challenge.images[0]); exampleImage.appendChild(exampleImg); header.appendChild(title); header.appendChild(controls); exampleSection.appendChild(header); exampleSection.appendChild(exampleImage); challengeView.appendChild(exampleSection); this.imageChallenge.currentImageChallenge = challenge; this.imageChallenge.totalImageChallenges = Math.floor((challenge.images.length - 1) / 9); this.imageChallenge.currentImageChallengeIndex = 0; this.imageChallenge.selectedImagesPerChallenge = Array( this.imageChallenge.totalImageChallenges ) .fill() .map(() => new Set()); } const progressOverlay = document.createElement('div'); progressOverlay.className = 'progress-overlay'; const progressContent = document.createElement('div'); progressContent.className = 'progress-overlay-content'; const progressText = document.createElement('div'); progressText.className = 'progress-overlay-text'; progressText.textContent = '0 done'; const progressBar = document.createElement('div'); progressBar.className = 'progress-overlay-bar'; const progressFill = document.createElement('div'); progressFill.className = 'progress-overlay-fill'; progressFill.style.width = '0%'; progressBar.appendChild(progressFill); progressContent.appendChild(progressText); progressContent.appendChild(progressBar); progressOverlay.appendChild(progressContent); if (config.logo !== undefined || config.title !== undefined) { const brandSection = document.createElement('div'); brandSection.className = 'progress-overlay-brand'; if (config.logo !== undefined) { const brandLogo = document.createElement('img'); brandLogo.src = config.logo; if (config.title !== undefined) { brandLogo.alt = config.title; } brandSection.appendChild(brandLogo); } if (config.title !== undefined) { const brandTitle = document.createElement('div'); brandTitle.className = 'progress-overlay-brand-title'; brandTitle.textContent = config.title; brandSection.appendChild(brandTitle); } progressOverlay.appendChild(brandSection); } popup.appendChild(progressOverlay); const infoView = this.createInfoView(popup); popup.appendChild(infoView); if (isAudioChallenge) { const soundChallengeView = this.audioChallenge.createSoundChallengeView(popup); soundChallengeView.classList.add('sound-challenge-view'); popup.appendChild(soundChallengeView); this.currentView = 'sound-challenge'; setTimeout(() => { this.audioChallenge.updateExistingAudioPopup(challenge); }, 0); } else if (isImageChallenge) { popup.appendChild(challengeView); this.currentView = 'challenge'; const grid = document.createElement('div'); grid.className = 'challenge-grid'; const verifyButton = document.createElement('button'); verifyButton.className = 'verify-button'; verifyButton.disabled = true; verifyButton.textContent = this.imageChallenge.totalImageChallenges > 1 ? 'NEXT' : 'VERIFY'; verifyButton.addEventListener('click', () => { const overlay = this.activePopup.querySelector('.progress-overlay'); const overlayText = overlay.querySelector('.progress-overlay-text'); const overlayFill = overlay.querySelector('.progress-overlay-fill'); overlay.classList.add('active'); if ( this.imageChallenge.currentImageChallengeIndex < this.imageChallenge.totalImageChallenges - 1 ) { overlayText.textContent = `${this.imageChallenge.currentImageChallengeIndex + 1} done`; const progress = ((this.imageChallenge.currentImageChallengeIndex + 1) / this.imageChallenge.totalImageChallenges) * 100; overlayFill.style.width = `${progress}%`; setTimeout(() => { overlay.classList.remove('active'); this.imageChallenge.currentImageChallengeIndex++; this.imageChallenge.updateGrid( grid, challenge, this.imageChallenge.currentImageChallengeIndex, verifyButton ); }, 1000); } else { overlayText.textContent = `${this.imageChallenge.totalImageChallenges} done`; overlayFill.style.width = '100%'; setTimeout(() => { this.imageChallenge.handleVerification(container, popup); }, 1000); } }); challengeView.appendChild(grid); challengeView.appendChild(verifyButton); this.imageChallenge.updateGrid(grid, challenge, 0, verifyButton); } document.body.appendChild(popup); this.positionPopup(popup, container); popup.style.visibility = 'visible'; popup.classList.add('active'); this._resizeHandler = () => { if (popup && popup.isConnected) { this.positionPopup(popup, container); } else { this.cleanup(); } }; window.addEventListener('resize', this._resizeHandler); return popup; } handleVerification(container, popup) { try { this.pauseAudioIfPlaying(popup); if (this.currentImagePowChallenge && this.currentImageChallenge) { const selectedImagesData = this.selectedImagesPerChallenge.map((set, index) => ({ challenge: index + 1, selectedIndexes: Array.from(set), })); console.log('Verification data:', { powChallenge: this.currentPowChallenge, imageChallenge: this.currentImageChallenge, selectedImages: selectedImagesData, }); } popup.classList.remove('active'); this.selectedImagesPerChallenge = []; this.currentChallengeIndex = 0; const checkbox = container.querySelector('.captcha-checkbox'); if (checkbox) { checkbox.checked = true; } } catch (error) { console.error('Verification error:', error); this.showError(container, 'Failed to verify challenge'); } } createInfoView(popup) { const icons = this.createSVGIcons(); const config = popup.config; const infoContent = document.createElement('div'); infoContent.className = 'info-content'; if (popup.hasAttribute('data-theme')) { const theme = popup.getAttribute('data-theme'); infoContent.setAttribute('data-theme', theme); } const infoHeader = document.createElement('div'); infoHeader.className = 'info-header'; const backButton = document.createElement('button'); backButton.className = 'back-button'; backButton.innerHTML = `${icons.back} Back to challenge`; backButton.addEventListener('click', () => { if (this.previousView === 'sound-challenge') { this.showSoundChallengeView(popup); } else { this.showChallengeView(popup); } }); infoHeader.appendChild(backButton); const mainContent = document.createElement('div'); mainContent.className = 'info-main-content'; if (config.logo !== undefined || config.title !== undefined) { const logoContainer = document.createElement('div'); logoContainer.className = 'info-logo-container'; const brandWrapper = config.url !== undefined ? document.createElement('a') : document.createElement('div'); if (config.url !== undefined) { brandWrapper.href = encodeURI(config.url); brandWrapper.target = '_blank'; brandWrapper.rel = 'noopener noreferrer'; brandWrapper.className = 'brand-wrapper'; } if (config.logo !== undefined) { const logo = document.createElement('img'); logo.className = 'info-logo'; logo.src = encodeURI(config.logo); if (config.title !== undefined) { logo.alt = config.title; } brandWrapper.appendChild(logo); } if (config.title !== undefined) { const brandTitleEl = document.createElement('div'); brandTitleEl.className = 'info-brand-title'; brandTitleEl.textContent = config.title; brandWrapper.appendChild(brandTitleEl); } logoContainer.appendChild(brandWrapper); mainContent.appendChild(logoContainer); } if (config.description !== undefined) { const infoText = document.createElement('div'); infoText.className = 'info-text'; infoText.textContent = config.description; mainContent.appendChild(infoText); } if (config.githubUrl !== undefined) { const githubContainer = document.createElement('div'); githubContainer.className = 'github-container'; const githubButton = document.createElement('a'); githubButton.href = encodeURI(config.githubUrl); githubButton.target = '_blank'; githubButton.rel = 'noopener noreferrer'; githubButton.className = 'github-button'; githubButton.innerHTML = `<svg viewBox="0 0 24 24" width="20" height="20"><path fill="currentColor" d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.