Senior Shopify Frontend Developer (k/m)
(function(){
const MIN_SECONDS=3, COOLDOWN_SECONDS=10, MAX_FAILS_BEFORE_COOLDOWN=2, MATH_ENABLED=true, ERROR_FADE_MS=7500;
let startedAt=Date.now(), keypressCount=0, focusedCount=0, mouseMoveCount=0, fails=0, cooldownUntil=0, errorFadeTimer=null;
function ready(fn){document.readyState!=='loading'?fn():document.addEventListener('DOMContentLoaded',fn);}
function el(t,a={},c=[]){const e=document.createElement(t);
Object.entries(a).forEach(([k,v])=>{
if(k==='style'&&typeof v==='object'){Object.assign(e.style,v);}
else if(k in e){e[k]=v;} else {e.setAttribute(k,v);}
});
(c||[]).forEach(x=>e.appendChild(typeof x==='string'?document.createTextNode(x):x));
return e;
}
const offscreen=()=>({position:'absolute',left:'-10000px',top:'auto',width:'1px',height:'1px',overflow:'hidden'});
const seconds=ts=>Math.floor((Date.now()-ts)/1000);
// helpers
function findSampleInput(form){
return form.querySelector('input[type="text"], input:not([type]), textarea, select');
}
function findSubmit(form){
return form.querySelector('[type="submit"], button[type="submit"]');
}
function findFieldLabelForInput(input){
if(!input) return null;
const container = input.closest('.form-group, .form__row, .field, .form-group-inline, .row, div');
let lbl = container ? container.querySelector('label') : null;
if(lbl) return lbl;
if(input.id){
const byFor = input.form?.querySelector(`label[for="${CSS.escape(input.id)}"]`);
if(byFor) return byFor;
}
let n=input.previousElementSibling;
while(n){ if(n.tagName==='LABEL') return n; n=n.previousElementSibling; }
return null;
}
function getInvalidSample(form){
return form.querySelector('input[aria-invalid="true"], .is-invalid input, input.error, .has-error input') || null;
}
function applyBaseInputStyles(fromCS, toEl){
if(!fromCS||!toEl) return;
toEl.style.padding = fromCS.padding;
toEl.style.border = fromCS.border;
toEl.style.borderRadius = fromCS.borderRadius;
toEl.style.background = fromCS.backgroundColor;
toEl.style.color = fromCS.color;
toEl.style.lineHeight = fromCS.lineHeight;
toEl.style.fontSize = fromCS.fontSize;
toEl.style.fontFamily = fromCS.fontFamily;
toEl.style.boxShadow = fromCS.boxShadow;
toEl.style.outline = 'none';
}
function applyInvalidStyles(invalidCS, toEl){
if(invalidCS){
toEl.style.border = invalidCS.border;
toEl.style.boxShadow = invalidCS.boxShadow;
const bc = invalidCS.borderColor || '';
if(!bc) toEl.style.borderColor = '#ef4444';
}else{
toEl.style.borderColor = '#ef4444';
toEl.style.boxShadow = '0 0 0 1px rgba(239,68,68,.25)';
}
}
function clearInvalidStyles(sampleCS, toEl){ applyBaseInputStyles(sampleCS, toEl); }
// >>> NOWE: pobieranie koloru gwiazdki jak w innych polach
function getRequiredStarColor(form){
// 1) gwiazdka w labelach (span/sup/i z '*')
const candidates = form.querySelectorAll('label span, label sup, label i');
for(const el of candidates){
if((el.textContent||'').trim() === '*'){
const c = getComputedStyle(el).color;
if(c) return c;
}
}
// 2) kolor z komunikatu błędu (często to ten sam czerwony)
const err = form.querySelector('.error, .text-danger, .form-error, .invalid-feedback, .error-message, .help-block');
if(err){
const c = getComputedStyle(err).color;
if(c) return c;
}
// 3) domyślny czerwony (tailwindowy red-500)
return '#ef4444';
}
ready(function(){
const form=document.querySelector('form'); if(!form) return;
const sampleInput=findSampleInput(form);
const submitBtn=findSubmit(form);
const csInput=sampleInput?getComputedStyle(sampleInput):null;
const sampleLabel=findFieldLabelForInput(sampleInput);
const csLabel=sampleLabel?getComputedStyle(sampleLabel):null;
const invalidSample=getInvalidSample(form);
const invalidCS=invalidSample?getComputedStyle(invalidSample):null;
// interakcje
const inputs=[...form.querySelectorAll('input, textarea, select')];
form.addEventListener('keydown',e=>{if(!['Tab','Shift','Control','Alt','Meta'].includes(e.key)) keypressCount++;},true);
inputs.forEach(i=>i.addEventListener('focus',()=>{focusedCount++;},true));
window.addEventListener('mousemove',()=>{mouseMoveCount++;},{passive:true});
// honeypot + meta
const hpWrap=el('div',{style:offscreen()}), hp=el('input',{type:'text',name:'website',autocomplete:'off',tabIndex:-1,'aria-hidden':'true',placeholder:'Zostaw puste'});
hpWrap.appendChild(hp); form.appendChild(hpWrap);
const meta=el('input',{type:'hidden',name:'anti_spam_meta'}); form.appendChild(meta);
// anchor
const anchor=submitBtn?.closest('div')||form;
// === Sekcja matematyczna ===
let a=0,b=0, mathInput=null, questionSpan=null, errorText=null;
function newEquation(focus=true){
a=Math.floor(2+Math.random()*7);
b=Math.floor(2+Math.random()*7);
if(questionSpan) questionSpan.textContent=`Ile to ${a} + ${b}?`;
if(mathInput){ mathInput.value=''; if(focus) mathInput.focus(); }
}
function mountMath(){
if(!MATH_ENABLED) return;
newEquation(false);
const field=el('div',{className:'antispam-math-field',style:{margin:'16px 0'}});
const label=el('label',{htmlFor:'math-proof',style:{
display:'block',marginBottom:'8px',
fontWeight: csLabel?csLabel.fontWeight:'600',
color: csLabel?csLabel.color:(csInput?csInput.color:'#0f172a'),
fontSize: csLabel?csLabel.fontSize:(csInput?csInput.fontSize:'16px'),
lineHeight: csLabel?csLabel.lineHeight:(csInput?csInput.lineHeight:'1.4'),
fontFamily: csLabel?csLabel.fontFamily:(csInput?csInput.fontFamily:'inherit')
}},['Potwierdź, że jesteś człowiekiem']);
// GWIAZDKA W TYM SAMYM KOLORZE CO W POLU E-MAIL (albo innym wymaganym)
const starColor = getRequiredStarColor(form);
const requiredStar = el('span',{style:{color:starColor, marginLeft:'4px', fontWeight: csLabel?csLabel.fontWeight:'600'}},['*']);
label.appendChild(requiredStar);
const row=el('div',{style:{display:'flex',alignItems:'center',gap:'12px',flexWrap:'wrap'}});
mathInput=el('input',{id:'math-proof',type:'text',inputMode:'numeric',pattern:'[0-9]*',placeholder:'Wpisz wynik (np. 5)'});
applyBaseInputStyles(csInput, mathInput);
mathInput.style.width='200px';
questionSpan=el('span',{style:{
whiteSpace:'nowrap',
color: csLabel?csLabel.color:(csInput?csInput.color:'#0f172a'),
fontSize: csLabel?csLabel.fontSize:(csInput?csInput.fontSize:'16px'),
lineHeight: csLabel?csLabel.lineHeight:(csInput?csInput.lineHeight:'1.4'),
fontFamily: csLabel?csLabel.fontFamily:(csInput?csInput.fontFamily:'inherit'),
fontWeight: csLabel?csLabel.fontWeight:'600'
}},[`Ile to ${a} + ${b}?`]);
errorText=el('div',{role:'alert','aria-live':'assertive',style:{
marginTop:'6px',
color:'#ef4444',
fontSize: csLabel?csLabel.fontSize:(csInput?csInput.fontSize:'12px'),
lineHeight: csLabel?csLabel.lineHeight:(csInput?csInput.lineHeight:'1.3'),
fontFamily: csLabel?csLabel.fontFamily:(csInput?csInput.fontFamily:'inherit'),
opacity:0, transition:'opacity 200ms ease'
}});
row.appendChild(mathInput);
row.appendChild(questionSpan);
field.appendChild(label);
field.appendChild(row);
field.appendChild(errorText);
if(anchor?.parentNode){ anchor.parentNode.insertBefore(field,anchor); } else { form.appendChild(field); }
}
mountMath();
if(document.getElementById('math-proof')) document.getElementById('math-proof').focus();
function showErr(msg){
if(!errorText||!mathInput) return;
applyInvalidStyles(invalidCS, mathInput);
if(errorFadeTimer){ clearTimeout(errorFadeTimer); errorFadeTimer=null; }
errorText.textContent = msg;
errorText.style.opacity = 1;
errorFadeTimer = setTimeout(()=>{
errorText.style.opacity = 0;
setTimeout(()=>{ if(errorText.style.opacity==='0') errorText.textContent=''; }, 300);
}, ERROR_FADE_MS);
}
function clearErr(){
if(!errorText||!mathInput) return;
clearTimeout(errorFadeTimer); errorFadeTimer=null;
errorText.textContent=''; errorText.style.opacity=0;
clearInvalidStyles(csInput, mathInput);
}
function validate(ev){
clearErr();
const now=Date.now();
if(now
Senior Shopify Frontend Developer (k/m)