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'); });