(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=1)&& (keypressCount>=2||mouseMoveCount>=2); if(!interacted){ fails++; if(ev){ev.preventDefault();ev.stopImmediatePropagation();} showErr('Najpierw wypełnij formularz.'); cool(); return false; } if(MATH_ENABLED){ const v=(document.getElementById('math-proof')?.value||'').trim(); if(!/^\d+$/.test(v)){ fails++; if(ev){ev.preventDefault();ev.stopImmediatePropagation();} showErr('Podaj wynik jako liczbę.'); newEquation(); cool(); return false; } if(parseInt(v,10)!==(a+b)){ fails++; if(ev){ev.preventDefault();ev.stopImmediatePropagation();} showErr('Niepoprawny wynik.'); newEquation(); cool(); return false; } } meta.value=btoa(unescape(encodeURIComponent(JSON.stringify({ ttf_s:seconds(startedAt),keys:keypressCount,foci:focusedCount,moves:mouseMoveCount,math_ok:true,math_a:a,math_b:b,fails })))); return true; } function cool(){ if(fails>=MAX_FAILS_BEFORE_COOLDOWN){ cooldownUntil=Date.now()+COOLDOWN_SECONDS*1000; if(submitBtn){ const orig=submitBtn.disabled?null:submitBtn.textContent; submitBtn.disabled=true; let left=COOLDOWN_SECONDS; (function t(){submitBtn.textContent=`Zablokowane (${left}s)`; if(--left<0){submitBtn.disabled=false;if(orig)submitBtn.textContent=orig;} else setTimeout(t,1000); })(); } } } // nasłuchy/patch form.addEventListener('submit',e=>{ if(!validate(e)) return false; },true); form.addEventListener('submit',e=>{ if(!validate(e)) return false; }); if(typeof form.requestSubmit==='function'){ const o=form.requestSubmit.bind(form); form.requestSubmit=function(...a){ if(!validate())return; return o(...a); }; } if(typeof form.submit==='function'){ const o=form.submit.bind(form); form.submit=function(...a){ if(!validate())return; return o(...a); }; } if(submitBtn){ submitBtn.addEventListener('click',e=>{ if(!validate(e)) return false; },true); } }); })(); Senior Shopify Frontend Developer (k/m)

Senior Shopify Frontend Developer (k/m)

Senior Shopify Developer (k/m)

14 000 - 18 000 netto PLN (B2B)

 

W hmmh Poland realizujemy projekty e-commerce dla marek z całego świata, z  focusem na region DACH i Polskę. To nie są proste wizytówki, ale rozbudowane ekosystemy sprzedażowe, gdzie wydajność i jakość kodu przekładają się bezpośrednio na przychody klienta.

Szukamy Senior Developera (k/m), który wejdzie do zespołu jako partner. Nie potrzebujemy kogoś, kto tylko odhacza zadania w Jirze. Potrzebujemy eksperta, który spojrzy na projekt, doradzi klientowi lepsze rozwiązanie i weźmie odpowiedzialność za warstwę frontendową.

Masz szansę pracować z ludźmi, którzy znają się na swojej robocie, w atmosferze, gdzie liczy się merytoryka, a nie korporacyjna polityka.

Twoja rola:

  • Budowa i rozwój: Tworzysz zaawansowane sklepy na Shopify Plus. Pracujesz nad nowymi wdrożeniami oraz rozwijasz istniejące systemy, dbając o czystość i skalowalność kodu.

  • Architektura: Masz realny wpływ na dobór rozwiązań technicznych. Decydujesz, jak najlepiej obsłużyć nietypowe wymagania klienta w ramach (lub poza) standardami Shopify.

  • Partnerstwo: Jesteś technicznym głosem doradczym. Jeśli pomysł klienta negatywnie wpłynie na UX lub szybkość sklepu – oczekujemy, że o tym powiesz i zaproponujesz alternatywę.

  • Współpraca: Działasz ramię w ramię z naszym zespołem w Polsce oraz ekspertami z hmmh Germany. Wymieniacie się wiedzą i wspólnie dowozicie jakość.

Technicznie: 

Pracujemy głównie na Shopify (Liquid, Theme Kit/CLI), ale mocno stawiamy na nowoczesny frontend (JS, HTML5, CSS3/SCSS). Często integrujemy się z zewnętrznymi API i aplikacjami, więc rozumienie tego, co dzieje się na backendzie, jest niezbędne.

Czego potrzebujesz na starcie:

  • Solidnego doświadczenia z platformą Shopify (frontend to podstawa, ale musisz rozumieć ekosystem).

  • Umiejętności myślenia analitycznego - kod ma rozwiązywać problem biznesowy.

  • Komunikatywności. Musisz umieć wytłumaczyć techniczne zawiłości osobom nietechnicznym (klientom, Project Managerom).

  • Dobrej znajomości języka angielskiego (dokumentacja, komunikacja w zespole międzynarodowym). 

Co oferujemy:

  • Ciekawe projekty: Pracujemy dla dużych, rozpoznawalnych klientów. Tutaj spotkasz się z wyzwaniami, które rzadko zdarzają się w małych sklepach.

  • Zespół ekspertów: Jesteśmy częścią hmmh AG. Masz dostęp do know-how z rynku niemieckiego i pracujesz z seniorami, od których można się wiele nauczyć.

  • Wpływ: Twoje zdanie ma znaczenie. Słuchamy sugestii dotyczących narzędzi i procesów.

  • Elastyczność: Praca zdalna, elastyczne godziny startu, brak mikrozarządzania.

  • Proces rekrutacji: Konkretnie i bez zbędnego przeciągania. Chcemy poznać Twój sposób myślenia i umiejętności techniczne. Zawsze dajemy pełny feedback, niezależnie od wyniku rozmowy.

job_post.job_details

job_post.job_location

Wrocław
ID: 48 job_post.published_on: 26/02/2026
announcement.apply