librecap
Version:
An open-source CAPTCHA Box alternative designed with privacy and data protection in mind.
653 lines (539 loc) • 93.6 kB
JavaScript
/*!
* 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-.