09:41
●●● WiFi 🔋
Levain
by Brotliebling
Impressum & Datenschutz
Willkommen bei
Levain
♡ by Brotliebling
Dein lebendiger Begleiter für perfekten Sauerteig — von der ersten Fütterung bis zur perfekten Krume.

Was Levain
für dich tut

Fünf Werkzeuge. Ein Ziel: besser backen.

🔬

Krumenanalyse

KI liest dein Brot und gibt dir einen konkreten Tipp für den nächsten Backvorgang.

🧫

Starter-Analyse

Foto deines Starters — KI erkennt Bläschen, Kruste, Farbe und Aktivitätszustand.

🌱

Levain-Tracker

Kein starrer Plan. Levain berechnet deinen Peak basierend auf Temperatur und Ratio.

📖

Backbuch & Community

Scores, Ränge, deine Galerie und eine Community die Mehl liebt.

💡

KI-Coach

Personalisierte Tipps genau dann, wenn du sie brauchst.

Schritt 1 von 2
Wie heißt dein Starter?
Gib deinem Starter einen Namen — er verdient einen.
Auguste
Herbert
Hildegard
Bruno
Gretl
Max

Deine Küche

Damit Levain deinen Starter richtig einschätzen kann.

Küchentemperatur
Kühl
<20°C
Mittel
20–24°C
Warm
>24°C
Backfrequenz
Selten
1×/Mo
Regelmäßig
1–2×/Wo
Viel
3×+

Bereit,
Auguste.

Dein Starter
Auguste
Mittelwarme Küche · Regelmäßig

Levain kennt jetzt deinen Starter. Jede Empfehlung wird auf dich zugeschnitten.

Levain · by Brotliebling
Guten Morgen
Dein Starter
atmet.
Levain · Aktiv
Auguste
Zuletzt gefüttert · 24°C Küche
—%
Aktivität
Starter — Fütterung eintragen um Peak zu berechnen.
Starter-Alarm? Levain benachrichtigt dich beim Peak.
🌱StarterFütterung & Peak
🔬AnalysierenBrot & Starter
📖Backbuch3 Einträge
💡CoachKI-Tipps

Letzte Laibe

Alle →
Noch keine Laibe — analysiere deinen ersten Anschnitt 🍞
Starter · Auguste
Aktivitätsverlauf
Heute · Aktivitätsverlauf
+0h+2h+4h+6h+8h
Live Peak-Berechnung
Live
GefüttertPeakAbfall
—%
Fütterung eintragen um Peak zu berechnen.
+ Fütterung eintragen →

Fütterungshistorie

+ Fütterung eintragen & KI fragen
✓ Eingetragen! Auguste sagt danke.
KI · Echte Analyse
Analysieren
🍞 Krumenanalyse
🧫 Starter-Analyse
Kostenlose Analysen: 3
📷
Anschnitt fotografieren
Foto wählen oder Kamera öffnen
Krume analysieren lassen
🍞
Krume wird gelesen…
Tipp: Gleichmäßige Porung entsteht bei 75–80% Hydration.
🍞
Coach-Tipp
🧫
Starter fotografieren
Von oben oder seitlich — nach Fütterung
Starter analysieren lassen
🧫
Starter wird untersucht…
Tipp: Ein aktiver Starter hat viele kleine gleichmäßige Bläschen.
🧫
Empfehlung
Deine Galerie
Backbuch
0
Laibe
Ø Score
1
Woche
🌱
Rang: Frischling
3 Laibe bis Krumenkenner

Community

Live
Dein unsichtbarer Coach
Lernen,
wenn's zählt.
Jetzt relevant · Stockgare
"Gib dem Teig 20 weitere Minuten — Geduld ist die wichtigste Zutat."
Basiert auf deinem letzten Backvorgang
Für dich ausgewählt
✦ KI-Tipps für mich laden
Porung
Tipp
Warum ungleichmäßige Porung entsteht
Dichte Porung im unteren Drittel ist meist ein Zeichen von Untergare…
Starter
Sommer-Anpassungen für deinen Starter
Bei über 25°C verändert sich der Fütterungsrhythmus erheblich…
Formen
Präformierung: weniger ist mehr
Eine lockere Hand beim Vorformen liefert bessere Ergebnisse…
Lernfortschritt
🔬
Krumenanalyse
10%
🧫
Starter-Analyse
0%
Gare & Timing
5%
3 Analysen.
Zeit für mehr.
Du hast deine kostenlosen Analysen genutzt.
Unbegrenzte Krumen- & Starter-Analysen
KI-Fütterungsempfehlungen
Backbuch ohne Limit & Community
Fütterung eintragen
Verhältnis
1:1:1
1:2:2
1:3:3
1:5:5
Temperatur jetzt
18°C
20°C
22°C
24°C
26°C+
Beobachtung (optional)
KI-Empfehlung
Laib teilen
🍞
87
Roggen
Gestern
Teile diesen Laib oder kopiere den Text.
Dein Feedback
Du testest eine frühe Beta. Deine Meinung macht den Unterschied.
Analyse gut
Analyse fehlerhaft
Design gefällt
Zu langsam
Starter super
Feature fehlt
Würde empfehlen
Bug gefunden

Einstellungen

Dein Starter
🌱
Name
Auguste
Küchentemperatur
Kühl
Mittel
Warm
Backfrequenz
Selten
Regelmäßig
Viel
App
💬
Feedback geben
Version
2.0.0 · Brotliebling
Supabase (optional)
Für Community, Sync & Accounts. Project URL + Anon Key von supabase.com
Stripe Payment Link
OneSignal App ID (Push)
Daten
🗑
Alle Daten zurücksetzen
Levain · by Brotliebling
Impressum & Datenschutz
💬
// ═══════════════════════════════════════════════════ // V2.0 — SUPABASE + STRIPE + PUSH // ═══════════════════════════════════════════════════ // ── SUPABASE CONFIG (aus Einstellungen oder .env) ── // Trage deine Supabase URL + Anon Key in den Settings ein // oder setze sie hier direkt: let SUPA_URL = S.get('supaUrl',''); let SUPA_KEY = S.get('supaKey',''); let sb = null; // Supabase client async function initSupabase(){ if(!SUPA_URL||!SUPA_KEY){return;} try{ // Supabase v2 via CDN const {createClient} = await import('https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2/+esm'); sb = createClient(SUPA_URL, SUPA_KEY); const {data:{session}} = await sb.auth.getSession(); if(session?.user){ sbUser = session.user; sbOnLogin(session.user); } sb.auth.onAuthStateChange((_,session)=>{ sbUser = session?.user||null; if(sbUser) sbOnLogin(sbUser); else sbOnLogout(); }); }catch(e){console.warn('Supabase not configured',e);} } function sbOnLogin(user){ S.set('sbEmail',user.email); document.getElementById('authView').style.display='none'; document.getElementById('setEmailVal').textContent=user.email; document.getElementById('setLogoutRow').style.display='flex'; syncFromSupabase(); } function sbOnLogout(){ sbUser=null; S.set('sbEmail',''); document.getElementById('setEmailVal').textContent='Nicht eingeloggt'; document.getElementById('setLogoutRow').style.display='none'; } // ── AUTH ── let authMode = 'login'; function authTab(mode){ authMode = mode; const loginBtn = document.getElementById('authTabLogin'); const regBtn = document.getElementById('authTabReg'); const mainBtn = document.getElementById('authBtn'); if(mode==='login'){ loginBtn.style.background='var(--gold)';loginBtn.style.color='#1A1006'; regBtn.style.background='transparent';regBtn.style.color='var(--cd)'; mainBtn.textContent='Einloggen'; document.getElementById('authPass').placeholder='Passwort'; document.getElementById('authPass').autocomplete='current-password'; } else { regBtn.style.background='var(--gold)';regBtn.style.color='#1A1006'; loginBtn.style.background='transparent';loginBtn.style.color='var(--cd)'; mainBtn.textContent='Konto erstellen'; document.getElementById('authPass').placeholder='Passwort (min. 8 Zeichen)'; document.getElementById('authPass').autocomplete='new-password'; } } async function doAuth(){ const email = document.getElementById('authEmail').value.trim(); const pass = document.getElementById('authPass').value; const btn = document.getElementById('authBtn'); const err = document.getElementById('authErr'); err.style.display='none'; if(!email||!pass){showAuthErr('Bitte E-Mail und Passwort eingeben.');return;} if(pass.length<8){showAuthErr('Passwort mindestens 8 Zeichen.');return;} btn.textContent='…';btn.style.opacity='.6';btn.style.pointerEvents='none'; if(!sb){ // No Supabase configured — treat as guest with email stored S.set('guestEmail',email); authGuest(); return; } try{ let res; if(authMode==='register'){ res = await sb.auth.signUp({email,password:pass,options:{data:{name:sName}}}); if(!res.error && res.data?.user && !res.data.session){ showAuthErr('✓ Bestätigungs-E-Mail gesendet! Bitte Postfach prüfen.',true); btn.textContent='E-Mail gesendet';return; } } else { res = await sb.auth.signInWithPassword({email,password:pass}); } if(res.error) throw res.error; }catch(e){ showAuthErr(e.message==='Invalid login credentials'?'E-Mail oder Passwort falsch.':e.message||'Fehler beim Login.'); btn.textContent=authMode==='login'?'Einloggen':'Konto erstellen'; btn.style.opacity='1';btn.style.pointerEvents='auto'; } } function showAuthErr(msg,success=false){ const e=document.getElementById('authErr'); e.textContent=msg;e.style.display='block'; e.style.background=success?'rgba(92,107,58,.1)':'rgba(210,80,60,.1)'; e.style.borderColor=success?'rgba(92,107,58,.3)':'rgba(210,80,60,.3)'; e.style.color=success?'rgba(150,200,120,.9)':'rgba(240,180,160,.9)'; } function authGuest(){ document.getElementById('authView').style.display='none'; S.set('obDone',false); // force onboarding for new guests if(!S.get('obDone',false)){ document.getElementById('ob').style.display='flex'; } else { showApp(); } } // ── SUPABASE SYNC ── async function syncFromSupabase(){ if(!sb||!sbUser) return; try{ // Load user profile const {data:profile} = await sb.from('profiles').select('*').eq('id',sbUser.id).single(); if(profile){ sName = profile.starter_name||sName; kitchenT = profile.kitchen_temp||kitchenT; backF = profile.bake_freq||backF; isPro = profile.is_pro||false; S.set('name',sName); S.set('temp',kitchenT); S.set('freq',backF); S.set('pro',isPro); } // Load bakes const {data:remoteBakes} = await sb.from('bakes').select('*').eq('user_id',sbUser.id).order('created_at',{ascending:false}).limit(20); if(remoteBakes?.length) { bakes=remoteBakes; S.set('bakes',bakes); } // Load feedings const {data:remoteFeeds} = await sb.from('feedings').select('*').eq('user_id',sbUser.id).order('created_at',{ascending:false}).limit(10); if(remoteFeeds?.length) { feedings=remoteFeeds; S.set('feedings',feedings); } // Check pro status if(isPro) { freeLeft=999; updateCounter(); } applyName(); buildPulse(); updatePeak(); renderFeedings(); renderBakes(); updateCounter(); }catch(e){console.warn('Sync error',e);} } async function saveToSupabase(table, data){ if(!sb||!sbUser) return; try{ await sb.from(table).upsert({...data, user_id:sbUser.id}); }catch(e){console.warn('Save error',e);} } // ── COMMUNITY (SUPABASE) ── async function renderCommunity(){ const feed = document.getElementById('commFeed'); if(!feed) return; if(!sb){ feed.innerHTML='
Community nicht verfügbar — Supabase nicht konfiguriert
'; return; } feed.innerHTML='
Lädt…
'; try{ const {data:posts} = await sb.from('community_posts') .select('*, profiles(starter_name, city)') .order('created_at',{ascending:false}).limit(20); if(!posts?.length){ feed.innerHTML='
Noch keine Beiträge — sei der Erste! 🍞
'; return; } feed.innerHTML=''; const {data:myLikes} = sb ? await sb.from('likes').select('post_id').eq('user_id',sbUser?.id||'') : {data:[]}; const likedIds = new Set((myLikes||[]).map(l=>l.post_id)); posts.forEach(p=>{ const initials=(p.profiles?.starter_name||'?').substring(0,2).toUpperCase(); const ago=timeAgo(p.created_at); const isLiked=likedIds.has(p.id); const div=document.createElement('div');div.className='cp'; div.innerHTML=`
${initials}
${p.profiles?.starter_name||'Bäcker'}
${p.profiles?.city||''}
${p.score||'—'}
${p.bread_name||''}
${p.tip?`
"${p.tip}"
`:''}
${isLiked?'❤️':'🤍'} ${(p.likes_count||0)+(isLiked?0:0)}
${ago}
`; feed.appendChild(div); }); }catch(e){feed.innerHTML='
Fehler beim Laden
';} } async function togglePostLike(postId, el){ if(!sb||!sbUser){showAuthPrompt();return;} const isLiked = el.innerHTML.includes('❤️'); const cnt = parseInt(el.querySelector('span').textContent)||0; if(isLiked){ await sb.from('likes').delete().eq('post_id',postId).eq('user_id',sbUser.id); await sb.from('community_posts').update({likes_count:Math.max(0,cnt-1)}).eq('id',postId); el.innerHTML='🤍 '+(cnt-1)+''; } else { await sb.from('likes').insert({post_id:postId,user_id:sbUser.id}); await sb.from('community_posts').update({likes_count:cnt+1}).eq('id',postId); el.innerHTML='❤️ '+(cnt+1)+''; } } function timeAgo(ts){ const diff=(Date.now()-new Date(ts).getTime())/1000; if(diff<60)return'gerade eben'; if(diff<3600)return`vor ${Math.floor(diff/60)} Min.`; if(diff<86400)return`vor ${Math.floor(diff/3600)} Std.`; return`vor ${Math.floor(diff/86400)} Tagen`; } // Share to community after analysis async function shareToComm(type, result){ if(!sb||!sbUser){return;} if(!result?.score) return; try{ await sb.from('community_posts').insert({ user_id: sbUser.id, bread_name: result.verdict||'', score: result.score, tip: result.tipp?.substring(0,140)||'', type: type, likes_count: 0 }); }catch(e){console.warn('Share failed',e);} } // ── STRIPE PAYWALL ── const STRIPE_LINK = S.get('stripeLink','https://buy.stripe.com/EINTRAGEN'); // Stripe Payment Link function openUpgrade(){ const sheet = document.getElementById('freeOv'); if(sheet) sheet.classList.add('on'); } function goToStripe(){ if(STRIPE_LINK.includes('EINTRAGEN')){ alert('Stripe-Link noch nicht konfiguriert. Bitte in den Einstellungen hinterlegen.'); return; } window.open(STRIPE_LINK,'_blank'); closeOv('freeOv'); } // Check if user just came back from Stripe (URL param ?upgraded=1) function checkStripeReturn(){ const params = new URLSearchParams(location.search); if(params.get('upgraded')==='1'){ isPro=true; freeLeft=999; S.set('pro',true); S.set('free',999); updateCounter(); if(sb&&sbUser) sb.from('profiles').upsert({id:sbUser.id,is_pro:true}); history.replaceState({},'','/'); // Show success toast const t=document.getElementById('toast'); if(t){t.textContent='🎉 Premium aktiviert! Unbegrenzte Analysen.';t.style.display='block';setTimeout(()=>t.style.display='none',4000);} } } // ── PUSH NOTIFICATIONS (OneSignal) ── async function initPush(){ if(!window.OneSignal) return; await window.OneSignal.init({ appId: S.get('onesignalId',''), safari_web_id: '', notifyButton:{enable:false}, serviceWorkerPath: '/sw.js', serviceWorkerUpdaterPath: '/sw.js', }); } async function requestPushPermission(){ S.set('nAsked',true); dismissNS(); if(!S.get('onesignalId','')){ // Fallback: native Notification API try{ const p = await Notification.requestPermission(); if(p==='granted') new Notification('Levain 🍞',{body:sName+' — Peak-Alarm aktiviert!'}); }catch(e){} return; } try{ await window.OneSignal.showNativePrompt(); }catch(e){} } async function askNotif(){ await requestPushPermission(); } // ── SAVE BAKE TO SUPABASE ── const _origSaveBB = typeof saveBB === 'function' ? saveBB : null; function saveBB(type){ const r=window._lastRes&&window._lastRes[type];if(!r)return; const now=new Date(); const ds=now.getDate()+'. '+['Jan','Feb','Mär','Apr','Mai','Jun','Jul','Aug','Sep','Okt','Nov','Dez'][now.getMonth()]; const bake = {id:'local_'+Date.now(), score:r.score||0, verdict:(r.verdict||'').substring(0,22), date:ds, tag:(r.verdict||'').substring(0,18), type}; bakes.unshift(bake); if(bakes.length>20)bakes.pop(); S.set('bakes',bakes); renderBakes(); nav('backbuch'); // Save to Supabase if(sb&&sbUser){ saveToSupabase('bakes',{bread_name:r.verdict||'',score:r.score||0,tag:r.verdict?.substring(0,18)||'',bake_date:now.toISOString(),analysis_type:type}); shareToComm(type,r); } } // ── HELPER: update home bakes ── const _origRenderBakes = typeof renderBakes === 'function' ? renderBakes : null; function renderBakes(){ const grid=document.getElementById('bg'); const homeList=document.getElementById('homeLastBakes'); if(grid){ grid.querySelectorAll('.bc.saved').forEach(n=>n.remove()); if(bakes.length===0){ if(!grid.querySelector('.empty-bb')){ const e=document.createElement('div');e.className='empty-bb'; e.style.cssText='grid-column:1/-1;text-align:center;padding:24px 0;font-size:13px;color:rgba(232,216,184,.3);'; e.textContent='Noch keine Laibe — analysiere deinen ersten Anschnitt 🍞'; grid.appendChild(e); } } else { grid.querySelector('.empty-bb')?.remove(); bakes.forEach(b=>{ const div=document.createElement('div');div.className='bc saved'; div.onclick=()=>shareB(b.score,'Laib vom '+(b.date||''),b.tag||'',b.date||''); const ico=b.type==='starter'?'🧫':'🍞'; div.innerHTML=`
${ico}
${b.score}
${b.bread_name||b.verdict||'Laib'}
${b.date||''}
${b.tag||''}
`; grid.prepend(div); }); } } // Update home last bakes if(homeList){ if(bakes.length===0){ homeList.innerHTML='
Noch keine Laibe — analysiere deinen ersten Anschnitt 🍞
'; } else { homeList.innerHTML=bakes.slice(0,3).map(b=>`
${b.type==='starter'?'🧫':'🍞'}
${b.bread_name||b.verdict||'Laib'}
${b.date||''}
${b.score}
`).join(''); } } const total=bakes.length; const cntEl=document.getElementById('bbCnt');if(cntEl)cntEl.textContent=total; const hbEl=document.getElementById('hBBs');if(hbEl)hbEl.textContent=total+(total===1?' Eintrag':' Einträge'); const scores=bakes.map(b=>b.score).filter(Boolean); const avgEl=document.getElementById('bbAvg');if(avgEl)avgEl.textContent=scores.length?Math.round(scores.reduce((a,b)=>a+b,0)/scores.length):'—'; updateRank(total); } // ── AUTH PROMPT (when guest tries community feature) ── function showAuthPrompt(){ const ov=document.getElementById('freeOv'); if(!ov) return; ov.querySelector('.sh-ttl').innerHTML='Konto erstellen'; ov.querySelector('.sh-sub').textContent='Melde dich an um die Community zu nutzen und deine Backhistorie zu synchronisieren.'; ov.querySelector('.sh-btn').textContent='Anmelden / Registrieren'; ov.querySelector('.sh-btn').onclick=()=>{closeOv('freeOv');document.getElementById('authView').style.display='flex';}; ov.querySelector('.sh-close').onclick=()=>closeOv('freeOv'); ov.classList.add('on'); } // ── LOGOUT ── async function doLogout(){ if(sb) await sb.auth.signOut(); sbUser=null; document.getElementById('authView').style.display='flex'; document.getElementById('app').style.display='none'; document.getElementById('ob').style.display='none'; closeOv('setOv'); } // ── SHOW APP (after auth) ── function showApp(){ S.set('obDone',true); document.getElementById('ob').style.display='none'; document.getElementById('app').style.display='flex'; applyName();buildPulse();updatePeak();renderFeedings();renderBakes();renderCommunity();updateCounter(); setInterval(updatePeak,60000); checkStripeReturn(); } // ── INIT OVERRIDE ── const _origInit = init; async function init(){ // First init Supabase await initSupabase(); // Check if we have a session if(sb && sbUser){ document.getElementById('authView').style.display='none'; if(S.get('obDone',false)){ showApp(); } else { document.getElementById('ob').style.display='flex'; } return; } // No Supabase configured — check local state if(!SUPA_URL || !SUPA_KEY){ document.getElementById('authView').style.display='none'; // skip auth if no Supabase if(S.get('obDone',false)){ showApp(); } else { document.getElementById('ob').style.display='flex'; } return; } // Show auth screen document.getElementById('authView').style.display='flex'; } // Kick off v2 init on page load document.addEventListener('DOMContentLoaded', () => { checkStripeReturn(); initPush(); }); // ── SERVICE WORKER ── if('serviceWorker' in navigator){ window.addEventListener('load',()=>{ navigator.serviceWorker.register('/sw.js').catch(()=>{}); }); } // ── OFFLINE ── function updateOnline(){ const b=document.getElementById('offlineBar'); if(b) b.classList.toggle('show',!navigator.onLine); } window.addEventListener('online', updateOnline); window.addEventListener('offline', updateOnline); // ── COOKIE BANNER ── function acceptCookies(){ localStorage.setItem('lv_cookies','1'); const b=document.getElementById('cookieBar');if(b)b.classList.add('hide'); } if(localStorage.getItem('lv_cookies')==='1'){ const b=document.getElementById('cookieBar');if(b)b.classList.add('hide'); } // ── PWA INSTALL ── let deferredPrompt=null; window.addEventListener('beforeinstallprompt',e=>{ e.preventDefault();deferredPrompt=e; if(!localStorage.getItem('lv_noinstall')){ setTimeout(()=>{const b=document.getElementById('installBar');if(b)b.classList.add('show');},3000); } }); const installBtn=document.getElementById('installBtn'); if(installBtn) installBtn.addEventListener('click',async()=>{ if(!deferredPrompt)return; deferredPrompt.prompt(); await deferredPrompt.userChoice; deferredPrompt=null; const b=document.getElementById('installBar');if(b)b.classList.remove('show'); }); function dismissInstall(){ const b=document.getElementById('installBar');if(b)b.classList.remove('show'); localStorage.setItem('lv_noinstall','1'); } window.addEventListener('appinstalled',()=>{ const b=document.getElementById('installBar');if(b)b.classList.remove('show'); });