Přeskočit na obsah
Transparentně. Prakticky. S respektem k času učitelů.

O nás: Schoolio staví online školy, které vydrží

Začali jsme jako tým lidí, kteří měli za sebou desítky kurzů, workshopů a malých akademií. Všude se opakoval stejný problém: skvělé know‑how, ale zbytečně komplikované procesy. Naší misí je dát lektorům a školám jednoduchý, moderní a spolehlivý systém, který se dá vysvětlit lidsky a používat denně bez stresu.

Zpět na hlavní stránku
Podpora
7/7
Rozumná reakční doba, jasné postupy.
Provoz
EU
Data a hosting v souladu s pravidly.
Zaměření
Škálování
Od 1 kurzu po online akademii.
Rychlý přehled
Co je pro nás důležité každý den
Stav
1
Srozumitelnost
Vysvětlujeme věci tak, aby šly hned použít.
2
Bezpečnost
Krok za krokem, bez zkratek a rizik.
3
Dlouhodobost
Rozhodnutí děláme s výhledem na roky.
4
Měřitelnost
Všechno má metriky a jasné cíle.
Odpočítávání do dalšího zlepšení
Každý týden vydáváme drobné iterace.
0
dní
0
hod
0
min
0
sek
Aktuální cyklus:

Mise a hodnoty

Nejde nám o “víc funkcí”. Jde nám o systém, který se chová předvídatelně, drží slovo a nezpomaluje výuku. Proto stavíme kolem čtyř pevných hodnot.

Jasnost bez zjednodušování
Dokumentace, UI a podpora v jedné linii.
Rozbalit
Když něco nejde vysvětlit na 2–3 kroky, bereme to jako signál, že jsme produkt navrhli špatně. Proto průběžně zjednodušujeme bez ztráty důležitých detailů.
Odpovědnost za výsledek
Nejen dodání, ale i dopad.
Rozbalit
Měříme onboarding, dokončení lekcí, výkon a podporu. Pokud metriky klesají, je to náš problém — a řešíme ho dřív, než se promění ve stížnost.
Zabezpečení jako standard
Bez kompromisů u dat a přístupů.
Rozbalit
Role, auditní logy, šifrování a pravidelný review přístupů. Bezpečnost není “projekt na později”, ale průběžná práce.
Respekt k času
Rychlost, automatizace, minimum ručních kroků.
Rozbalit
Každá opakovaná činnost je kandidát na automatizaci. Nástroje mají sloužit výuce — ne učitelé nástrojům.

Interaktivní časová osa

Upřímně: naše historie není o “velkých slovech”, ale o malých rozhodnutích. Rozklikněte si milníky a podívejte se, co se v nich skrývá.

Tip: Shift+klik otevře milník a zavře ostatní.
2019 — První experimenty s online výukou
Rozbalit
Pochopili jsme, co lektory opravdu brzdí: chaos v materiálech, přehled studentů a platby.
Testovali jsme různé kombinace nástrojů a zjistili, že “skládání” systému z pěti služeb má vysokou cenu: onboarding je pomalý a školy se v tom brzy ztratí. Zrodila se myšlenka sjednocení do jednoho prostředí.
Poznámky k lekcím Kontrola přístupů Jednoduché platby
2020 — Model “jedna škola, jeden systém”
Rozbalit
Začali jsme stavět standardy pro obsah, komunikaci a administraci.
Vyladili jsme proces od registrace po certifikát: šablony lekcí, automatická pravidla, notifikace a kontrolní seznamy. To, co dříve trvalo týdny, šlo najednou v dnech.
Zásada
Jedna pravda
Materiály, termíny i platby na jednom místě.
Zásada
Minimum ručně
Opakující se kroky automatizujeme.
Zásada
Měřit
Bez metrik se nedá zlepšovat.
2022 — Důraz na kvalitu obsahu a průchod studiem
Rozbalit
Zavedli jsme standardy pro lekce, úkoly a feedback tak, aby student věděl, co dělat.
Postavili jsme “návyk” pro studenty: jasný plán, krátké kroky a rychlý feedback. Školy díky tomu zlepšily dokončování kurzů bez nátlaku a bez marketingových triků.
Pravidlo pro lektory
Jedna lekce = jeden viditelný výsledek
Student musí odcházet s jasným “umím / mám hotovo / rozumím”.
2024 — Transparentní SLA a zodpovědná podpora
Rozbalit
Služba není “hotová”. Kvalita je rytmus a dohoda.
Zavedli jsme jasné časy reakce, pravidelnou údržbu a transparentní komunikaci při incidentech. Školy tak vědí, co mohou očekávat, a my víme, co musíme garantovat.
Krátké shrnutí pro sdílení
Schoolio vzniklo z praxe online výuky: sjednotit nástroje do jednoho systému, měřit dopad a zlepšovat každý týden. Stavíme bezpečně, srozumitelně a dlouhodobě.

Tým

Bez fotek záměrně — chceme, aby bylo jasné, co kdo dělá. Jsme malý tým, ale s vysokou disciplínou. Role jsou popsané tak, aby se dalo rychle pochopit, na koho se obrátit.

Kontakt
+420 732 581 964
Po–Ne 9:00–18:00 (CET)
Produkt
Roadmap
Vede prioritizaci, testuje v praxi, hlídá, aby nové funkce byly pochopitelné do 5 minut.
Zásada
Každá změna má měřitelný důvod
Engineering
Stabilita
Kód, performance, infrastruktura, bezpečnostní standardy a automatizace release procesů.
Zásada
Nezrychlujeme za cenu rizika
Podpora
SLA
Třídí požadavky, navrhuje postupy, zajišťuje, že odpovědi jsou stručné a použitelné.
Zásada
Odpověď dohodou, ne slibem
Obsah & pedagogika
Výuka
Pomáhá školám nastavovat strukturu lekcí, evaluaci a feedback. Dohlíží na srozumitelnost pro studenty.
Zásada
Student musí vědět co teď
Design
UI/UX
Vytváří UI, které se “neptá” — vede. Hlídá kontrast, čitelnost a konzistentní komponenty napříč školami.
Zásada
Nejlepší UI je tiché
Ops & compliance
Provoz
Správa prostředí, monitoring, pravidla pro přístupy, auditní stopa a procesy při změnách.
Zásada
Kontrola změn je součást kvality
Chcete vědět, jak přemýšlíme o kvalitě?
Přečtěte si mini‑přísahu a zeptejte se na cokoliv
Není to marketingový text. Je to krátký závazek, který používáme jako interní kontrolní seznam.
Napište nám

Krátká zpráva týmu Schoolio

Odpovíme e‑mailem. Bez spamu. Bez přeposílání.

0/1200
Odeslání je simulované pro demo stránku; data se nikam neodesílají. Zpráva se uloží jen lokálně ve vašem prohlížeči.
Mini‑přísaha kvality

Co slibujeme školám, které na nás spoléhají

Přísaha v 6 bodech
  1. 1Řekneme pravdu o možnostech i limitech a nenalákáme vás na funkce “brzy”.
  2. 2Opravy mají přednost před novinkami, pokud je ohrožena stabilita.
  3. 3Změny vysvětlíme lidsky a krátce — co se změnilo, proč a co z toho plyne.
  4. 4Data chráníme jako kdyby byla naše: role, přístupy, auditní stopa.
  5. 5Zpětná vazba má váhu — sbíráme ji, třídíme a vracíme s plánem.
  6. 6Kvalita je proces — každý týden zlepšení, každý měsíc revize.
Podepsat (lokálně)
“Beru kvalitu vážně.”
Uložíme jen záznam souhlasu v tomto prohlížeči.
Stav: Nepodepsáno
Check‑list (rychlá kontrola)
Zaškrtněte, co chcete mít splněné pro vaši školu.
Vybráno
Zatím nic.
`; const footerFallback = ` `; const bindThemeToggle = () => { const btn = document.querySelector('[data-theme-toggle]'); if(!btn) return; btn.addEventListener('click', () => { const root = document.documentElement; const next = root.classList.contains('dark') ? 'light' : 'dark'; store.set('schoolio_theme', next); applyTheme(next); }, {passive:true}); }; const initCookieBanner = () => { const key = 'schoolio_cookie_consent_v1'; const banner = $('#cookieBanner'); if(!banner) return; const existing = store.get(key, null); const show = () => { banner.classList.remove('hidden'); }; const hide = () => { banner.classList.add('hidden'); }; if(existing === null){ window.setTimeout(show, 650); } const acceptBtn = $('#cookieAccept'); const declineBtn = $('#cookieDecline'); const closeBtn = $('#cookieClose'); const setConsent = (value) => { store.set(key, {value, ts: Date.now()}); hide(); }; acceptBtn && acceptBtn.addEventListener('click', () => setConsent('accepted')); declineBtn && declineBtn.addEventListener('click', () => setConsent('declined')); closeBtn && closeBtn.addEventListener('click', () => setConsent('closed')); document.addEventListener('keydown', (e) => { if(e.key === 'Escape' && !banner.classList.contains('hidden')) setConsent('closed'); }); }; const initCountdown = () => { const d = $('#cdDays'), h = $('#cdHours'), m = $('#cdMins'), s = $('#cdSecs'); const cycleLabel = $('#cycleLabel'); const resetBtn = $('#resetCountdownBtn'); if(!d||!h||!m||!s||!cycleLabel||!resetBtn) return; const key = 'schoolio_iteration_cycle_v1'; const startNewCycle = () => { const now = new Date(); const next = new Date(now); const day = next.getDay(); const diffToMonday = (8 - day) % 7; next.setDate(next.getDate() + diffToMonday); next.setHours(9,0,0,0); if(next.getTime() <= now.getTime()){ next.setDate(next.getDate()+7); } const payload = { target: next.getTime(), created: now.getTime() }; store.set(key, payload); return payload; }; const getCycle = () => { const saved = store.get(key, null); if(!saved || !saved.target || typeof saved.target !== 'number') return startNewCycle(); if(saved.target - Date.now() < -60000) return startNewCycle(); return saved; }; let cycle = getCycle(); const fmt2 = (n) => String(Math.max(0, n)).padStart(2,'0'); const updateLabel = () => { const t = new Date(cycle.target); const created = new Date(cycle.created || Date.now()); const label = `${created.getFullYear()}-${String(created.getMonth()+1).padStart(2,'0')}-${String(created.getDate()).padStart(2,'0')} → ${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,'0')}-${String(t.getDate()).padStart(2,'0')} (Po 09:00)`; cycleLabel.textContent = label; }; updateLabel(); const tick = () => { const now = Date.now(); let delta = Math.max(0, cycle.target - now); if(delta === 0){ cycle = startNewCycle(); updateLabel(); delta = Math.max(0, cycle.target - Date.now()); } const sec = Math.floor(delta/1000); const days = Math.floor(sec/86400); const hours = Math.floor((sec%86400)/3600); const mins = Math.floor((sec%3600)/60); const secs = sec%60; d.textContent = String(days); h.textContent = fmt2(hours); m.textContent = fmt2(mins); s.textContent = fmt2(secs); }; tick(); const iv = window.setInterval(tick, 1000); resetBtn.addEventListener('click', () => { cycle = startNewCycle(); updateLabel(); tick(); }); window.addEventListener('beforeunload', () => window.clearInterval(iv), {once:true}); }; const initTimeline = () => { const wrap = $('#timelineWrap'); if(!wrap) return; const items = $$('details', wrap); const openAll = $('#openAllBtn'); const collapseAll = $('#collapseAllBtn'); const copyBtnA = $('#copySummaryBtn'); const copyBtnB = $('#copyShareBtn'); const copyToast = $('#copyToast'); const shareText = $('#shareText'); const setAll = (open) => items.forEach(d => d.open = open); openAll && openAll.addEventListener('click', () => setAll(true)); collapseAll && collapseAll.addEventListener('click', () => setAll(false)); const closeOthers = (current) => items.forEach(d => { if(d !== current) d.open = false; }); items.forEach(d => { const sum = $('summary', d); if(!sum) return; sum.addEventListener('click', (e) => { if(e.shiftKey){ window.setTimeout(() => { if(d.open) closeOthers(d); }, 0); } }); }); const toast = () => { if(!copyToast) return; copyToast.classList.remove('hidden'); window.clearTimeout(toast._t); toast._t = window.setTimeout(() => copyToast.classList.add('hidden'), 1200); }; const doCopy = async (text) => { try{ await navigator.clipboard.writeText(text); toast(); return true; }catch(e){ const ta = document.createElement('textarea'); ta.value = text; ta.setAttribute('readonly',''); ta.style.position = 'fixed'; ta.style.left = '-9999px'; document.body.appendChild(ta); ta.select(); let ok = false; try{ ok = document.execCommand('copy'); }catch(err){ ok = false; } document.body.removeChild(ta); if(ok) toast(); return ok; } }; const summaryText = () => { const years = items.map(d => d.getAttribute('data-year')).filter(Boolean); const unique = Array.from(new Set(years)).join(', '); const t = shareText ? shareText.textContent.trim() : ''; return `Schoolio — shrnutí\nRoky: ${unique}\n\n${t}`; }; const bindCopyBtn = (btn) => btn && btn.addEventListener('click', () => doCopy(summaryText())); bindCopyBtn(copyBtnA); bindCopyBtn(copyBtnB); }; const initOathDialog = () => { const dlg = $('#oathDialog'); if(!dlg) return; const openBtns = [$('#openOathBtn'), $('#openOathInlineBtn'), $('#openOathBottomBtn')].filter(Boolean); const closeBtn = $('#closeOathBtn'); const closeX = $('#closeOathX'); const signBtn = $('#signOathBtn'); const revokeBtn = $('#revokeOathBtn'); const oathState = $('#oathState'); const oathTime = $('#oathTime'); const chosen = $('#oathChosen'); const copyChecklistBtn = $('#copyOathChecklistBtn'); const copyToast = $('#oathCopyToast'); const key = 'schoolio_oath_signature_v1'; const keyChecklist = 'schoolio_oath_checklist_v1'; const escapeClose = (e) => { if(e.key === 'Escape') { try{ dlg.close(); }catch(err){} } }; const open = () => { if(typeof dlg.showModal === 'function') dlg.showModal(); else dlg.setAttribute('open',''); document.addEventListener('keydown', escapeClose); sync(); }; const close = () => { try{ dlg.close(); }catch(e){ dlg.removeAttribute('open'); } document.removeEventListener('keydown', escapeClose); }; openBtns.forEach(b => b.addEventListener('click', open)); closeBtn && closeBtn.addEventListener('click', close); closeX && closeX.addEventListener('click', close); dlg.addEventListener('click', (e) => { const rect = dlg.getBoundingClientRect(); const inDialog = (e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom); if(!inDialog) close(); }); const fmtTime = (ts) => { const d = new Date(ts); const dd = String(d.getDate()).padStart(2,'0'); const mm = String(d.getMonth()+1).padStart(2,'0'); const yy = d.getFullYear(); const hh = String(d.getHours()).padStart(2,'0'); const mi = String(d.getMinutes()).padStart(2,'0'); return `${dd}.${mm}.${yy} ${hh}:${mi}`; }; const getSelected = () => { const checkboxes = $$('input[data-oath-item]', dlg); return checkboxes.filter(c => c.checked).map(c => c.getAttribute('data-oath-item')); }; const setSelected = (arr) => { const set = new Set(Array.isArray(arr) ? arr : []); $$('input[data-oath-item]', dlg).forEach(c => { c.checked = set.has(c.getAttribute('data-oath-item')); }); }; const syncChosen = () => { const arr = getSelected(); store.set(keyChecklist, {items: arr, ts: Date.now()}); if(!chosen) return; if(arr.length === 0) chosen.textContent = 'Zatím nic.'; else chosen.textContent = arr.join(' • '); }; const sync = () => { const sig = store.get(key, null); if(oathState){ oathState.textContent = sig && sig.signed ? 'Podepsáno' : 'Nepodepsáno'; } if(oathTime){ oathTime.textContent = sig && sig.signed && sig.ts ? `( ${fmtTime(sig.ts)} )` : ''; } const savedChecklist = store.get(keyChecklist, null); setSelected(savedChecklist && savedChecklist.items ? savedChecklist.items : []); syncChosen(); }; signBtn && signBtn.addEventListener('click', () => { store.set(key, {signed: true, ts: Date.now()}); sync(); }); revokeBtn && revokeBtn.addEventListener('click', () => { store.set(key, {signed: false, ts: Date.now()}); sync(); }); $$('input[data-oath-item]', dlg).forEach(c => c.addEventListener('change', syncChosen)); const toast = () => { if(!copyToast) return; copyToast.classList.remove('hidden'); window.clearTimeout(toast._t); toast._t = window.setTimeout(() => copyToast.classList.add('hidden'), 1200); }; const copyText = async (text) => { try{ await navigator.clipboard.writeText(text); toast(); return true; }catch(e){ const ta = document.createElement('textarea'); ta.value = text; ta.setAttribute('readonly',''); ta.style.position = 'fixed'; ta.style.left = '-9999px'; document.body.appendChild(ta); ta.select(); let ok = false; try{ ok = document.execCommand('copy'); }catch(err){ ok = false; } document.body.removeChild(ta); if(ok) toast(); return ok; } }; copyChecklistBtn && copyChecklistBtn.addEventListener('click', () => { const sig = store.get(key, null); const items = getSelected(); const lines = [ 'Schoolio — mini‑přísaha (check‑list)', `Podepsáno: ${sig && sig.signed ? 'ano' : 'ne'}${sig && sig.ts ? ' ('+fmtTime(sig.ts)+')' : ''}`, '', items.length ? items.map((x,i)=>`${i+1}. ${x}`).join('\n') : 'Bez položek.' ]; copyText(lines.join('\n')); }); sync(); }; const initForm = () => { const form = $('#contactForm'); if(!form) return; const fName = $('#fName'), fEmail = $('#fEmail'), fMsg = $('#fMsg'), fConsent = $('#fConsent'); const eName = $('#eName'), eEmail = $('#eEmail'), eMsg = $('#eMsg'), eConsent = $('#eConsent'); const status = $('#formStatus'); const clearBtn = $('#clearDraftBtn'); const msgCount = $('#msgCount'); const key = 'schoolio_about_contact_draft_v1'; const setStatus = (kind, text) => { if(!status) return; status.classList.remove('hidden'); status.textContent = text; status.className = 'rounded-2xl px-4 py-3 text-sm font-semibold'; if(kind === 'ok'){ status.classList.add('bg-emerald-600','text-white'); }else if(kind === 'warn'){ status.classList.add('bg-amber-500','text-slate-950'); }else{ status.classList.add('bg-rose-600','text-white'); } window.clearTimeout(setStatus._t); setStatus._t = window.setTimeout(() => status.classList.add('hidden'), 2600); }; const isEmail = (v) => /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(String(v||'').trim()); const clamp = (v, max) => String(v||'').slice(0, max); const updateCount = () => { if(!msgCount || !fMsg) return; msgCount.textContent = String((fMsg.value || '').length); }; const saveDraft = () => { const payload = { name: clamp(fName.value, 60), email: clamp(fEmail.value, 120), msg: clamp(fMsg.value, 1200), consent: !!fConsent.checked, ts: Date.now() }; store.set(key, payload); }; const loadDraft = () => { const d = store.get(key, null); if(!d) return; if(fName) fName.value = d.name || ''; if(fEmail) fEmail.value = d.email || ''; if(fMsg) fMsg.value = d.msg || ''; if(fConsent) fConsent.checked = !!d.consent; updateCount(); }; const showErr = (el, msgEl, show) => { if(!el || !msgEl) return; if(show){ msgEl.classList.remove('hidden'); el.classList.add('ring-2','ring-rose-400'); }else{ msgEl.classList.add('hidden'); el.classList.remove('ring-2','ring-rose-400'); } }; const validate = () => { const nameOk = (fName.value||'').trim().length >= 2; const emailOk = isEmail(fEmail.value); const msgOk = (fMsg.value||'').trim().length >= 12; const consentOk = !!fConsent.checked; showErr(fName, eName, !nameOk); showErr(fEmail, eEmail, !emailOk); showErr(fMsg, eMsg, !msgOk); if(eConsent){ if(!consentOk) eConsent.classList.remove('hidden'); else eConsent.classList.add('hidden'); } return nameOk && emailOk && msgOk && consentOk; }; [fName, fEmail, fMsg].forEach(el => { if(!el) return; el.addEventListener('input', () => { saveDraft(); if(el === fMsg) updateCount(); }, {passive:true}); el.addEventListener('blur', () => validate(), {passive:true}); }); fConsent && fConsent.addEventListener('change', () => { saveDraft(); validate(); }); clearBtn && clearBtn.addEventListener('click', () => { store.del(key); if(fName) fName.value = ''; if(fEmail) fEmail.value = ''; if(fMsg) fMsg.value = ''; if(fConsent) fConsent.checked = false; updateCount(); validate(); setStatus('warn', 'Vymazáno (jen lokálně).'); }); form.addEventListener('submit', (e) => { e.preventDefault(); updateCount(); if(!validate()){ setStatus('err', 'Zkontrolujte prosím zvýrazněná pole.'); return; } const sentKey = 'schoolio_about_contact_sent_v1'; store.set(sentKey, {ts: Date.now(), payload: store.get(key, null)}); store.del(key); setStatus('ok', 'Hotovo! Zpráva byla uložena lokálně jako odeslaná.'); if(fName) fName.value = ''; if(fEmail) fEmail.value = ''; if(fMsg) fMsg.value = ''; if(fConsent) fConsent.checked = false; updateCount(); validate(); }); loadDraft(); updateCount(); }; const initNavButtons = () => { const jump = $('#jumpTeamBtn'); jump && jump.addEventListener('click', () => { const el = $('#team'); if(el) el.scrollIntoView({behavior:'smooth', block:'start'}); }); const scrollTopBtn = $('#scrollTopBtn'); scrollTopBtn && scrollTopBtn.addEventListener('click', () => window.scrollTo({top:0, behavior:'smooth'})); }; const initUptimeDot = () => { const dot = $('#uptimeDot'); if(!dot) return; let t = 0; const step = () => { t += 1; const green = (t % 8) < 7; dot.classList.toggle('bg-emerald-500', green); dot.classList.toggle('bg-amber-500', !green); }; step(); window.setInterval(step, 1500); }; const initYear = () => { const y = $('#yNow'); if(y) y.textContent = String(new Date().getFullYear()); }; initTheme(); Promise.all([ injectComponent('hdrMount', './header.html', headerFallback), injectComponent('ftrMount', './footer.html', footerFallback) ]).then(() => { bindThemeToggle(); }); initCookieBanner(); initCountdown(); initTimeline(); initOathDialog(); initForm(); initNavButtons(); initUptimeDot(); initYear(); })();