@@ -3,27 +3,29 @@
< head >
< head >
< meta charset = "utf-8" >
< meta charset = "utf-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1, viewport-fit=cover" >
< meta name = "viewport" content = "width=device-width, initial-scale=1, viewport-fit=cover" >
< title > TEIG//TERMINAL — Pizzateig-Kalkulator< / title >
< title > TEIG//TERMINAL v1.1 — Pizzateig-Kalkulator< / title >
< meta name = "description" content = "Pizzateig-Rechner mit Bäckerprozent, Mehl-Datenbank, Vorteig und Rückwärts-Zeitplan." >
< meta name = "description" content = "Pizzateig-Rechner mit Bäckerprozent, Mehl-Datenbank, Vorteig und Rückwärts-Zeitplan." >
< meta name = "color-scheme" content = "dark" >
< meta name = "color-scheme" content = "dark" >
< style >
< style >
: root {
: root {
--bg : #07080B ;
--bg : #07080B ;
--panel : #0E1117 ;
--panel : #10151F ;
--panel-2 : #121722 ;
--panel-2 : #151C29 ;
--line : #1B2430 ;
--line : #263241 ;
--line-hot : #24 3447 ;
--line-hot : #34475C ;
--cyan : #00F0FF ;
--cyan : #00F0FF ;
--magenta : #FF2D78 ;
--magenta : #FF2D78 ;
--amber : #FFB000 ;
--amber : #FFB000 ;
--lime : #8CFF3B ;
--lime : #8CFF3B ;
--txt : #C9D6E2 ;
--txt : #E2ECF5 ;
--dim : #6E7B8B ;
--dim : #A5B4C5 ;
--muted : #8796A8 ;
--mono : ui-monospace , "JetBrains Mono" , "SFMono-Regular" , "SF Mono" , Menlo , Consolas , "Liberation Mono" , monospace ;
--mono : ui-monospace , "JetBrains Mono" , "SFMono-Regular" , "SF Mono" , Menlo , Consolas , "Liberation Mono" , monospace ;
--r : 2 px ;
--r : 2 px ;
}
}
* { box-sizing : border-box }
* { box-sizing : border-box }
html , body { margin : 0 ; padding : 0 }
html , body { margin : 0 ; padding : 0 }
html { touch-action : manipulation }
body {
body {
background : var ( - - bg ) ;
background : var ( - - bg ) ;
color : var ( - - txt ) ;
color : var ( - - txt ) ;
@@ -37,6 +39,7 @@ body{
linear-gradient ( 90 deg , rgba ( 0 , 240 , 255 , .028 ) 1 px , transparent 1 px ) ;
linear-gradient ( 90 deg , rgba ( 0 , 240 , 255 , .028 ) 1 px , transparent 1 px ) ;
background-size : 32 px 32 px ;
background-size : 32 px 32 px ;
}
}
button , input , select , . chip , . stepper , . quick-summary { touch-action : manipulation }
. wrap { max-width : 760 px ; margin : 0 auto ; padding : 0 14 px }
. wrap { max-width : 760 px ; margin : 0 auto ; padding : 0 14 px }
/* ---------- Kopf ---------- */
/* ---------- Kopf ---------- */
@@ -48,8 +51,8 @@ header{padding:28px 0 18px;border-bottom:1px solid var(--line);margin-bottom:20p
text-shadow : 0 0 18 px rgba ( 0 , 240 , 255 , .35 ) ;
text-shadow : 0 0 18 px rgba ( 0 , 240 , 255 , .35 ) ;
}
}
. brand em { font-style : normal ; color : var ( - - magenta ) ; text-shadow : 0 0 18 px rgba ( 255 , 45 , 120 , .45 ) }
. brand em { font-style : normal ; color : var ( - - magenta ) ; text-shadow : 0 0 18 px rgba ( 255 , 45 , 120 , .45 ) }
. tag { color : var ( - - dim ) ; font-size : 11 px ; letter-spacing : .22 em ; text-transform : uppercase ; margin : 10 px 0 0 }
. tag { color : var ( - - muted ) ; font-size : 11 px ; letter-spacing : .22 em ; text-transform : uppercase ; margin : 10 px 0 0 }
. status { display : flex ; gap : 8 px ; align-items : center ; margin-top : 12 px ; font-size : 11 px ; letter-spacing : .14 em ; color : var ( - - dim ) ; text-transform : uppercase }
. status { display : flex ; gap : 8 px ; align-items : center ; margin-top : 12 px ; font-size : 11 px ; letter-spacing : .14 em ; color : var ( - - muted ) ; text-transform : uppercase }
. dot { width : 7 px ; height : 7 px ; background : var ( - - lime ) ; border-radius : 50 % ; box-shadow : 0 0 10 px var ( - - lime ) ; animation : pulse 2.4 s infinite }
. dot { width : 7 px ; height : 7 px ; background : var ( - - lime ) ; border-radius : 50 % ; box-shadow : 0 0 10 px var ( - - lime ) ; animation : pulse 2.4 s infinite }
@ keyframes pulse { 50 % { opacity : .25 } }
@ keyframes pulse { 50 % { opacity : .25 } }
@@ -79,7 +82,7 @@ header{padding:28px 0 18px;border-bottom:1px solid var(--line);margin-bottom:20p
. chips { display : flex ; flex-wrap : wrap ; gap : 6 px }
. chips { display : flex ; flex-wrap : wrap ; gap : 6 px }
. chip {
. chip {
font : inherit ; font-size : 12 px ;
font : inherit ; font-size : 12 px ;
background : transparent ; color : var ( - - dim ) ;
background : rgba ( 255 , 255 , 255 , .015 ) ; color : #B4C3D3 ;
border : 1 px solid var ( - - line - hot ) ; border-radius : var ( - - r ) ;
border : 1 px solid var ( - - line - hot ) ; border-radius : var ( - - r ) ;
padding : 7 px 11 px ; cursor : pointer ; letter-spacing : .06 em ;
padding : 7 px 11 px ; cursor : pointer ; letter-spacing : .06 em ;
transition : .15 s ;
transition : .15 s ;
@@ -90,11 +93,54 @@ header{padding:28px 0 18px;border-bottom:1px solid var(--line);margin-bottom:20p
box-shadow : 0 0 16 px rgba ( 0 , 240 , 255 , .35 ) ; font-weight : 700 ;
box-shadow : 0 0 16 px rgba ( 0 , 240 , 255 , .35 ) ; font-weight : 700 ;
}
}
/* ---------- UX-Modus & Schnellstart ---------- */
. quick-summary {
position : sticky ; top : 0 ; z-index : 20 ;
display : flex ; gap : 8 px ; overflow : auto ; align-items : center ;
margin : 0 -14 px 14 px ; padding : 8 px 14 px ;
background : rgba ( 7 , 8 , 11 , .92 ) ; backdrop-filter : blur ( 8 px ) ;
border-bottom : 1 px solid var ( - - line - hot ) ;
scrollbar-width : none ;
}
. quick-summary :: -webkit-scrollbar { display : none }
. quick-summary span {
white-space : nowrap ; border : 1 px solid var ( - - line - hot ) ; border-radius : var ( - - r ) ;
background : #0A0E15 ; color : var ( - - txt ) ; padding : 6 px 9 px ; font-size : 12 px ;
font-variant-numeric : tabular-nums ;
}
. quick-summary b { color : var ( - - cyan ) ; font-weight : 700 }
@ media ( max-width : 520px ) {
. quick-summary {
display : grid ;
grid-template-columns : 1 fr 1 fr ;
gap : 6 px ;
overflow : visible ;
padding : 8 px 14 px 10 px ;
}
. quick-summary span {
min-width : 0 ;
white-space : normal ;
line-height : 1.25 ;
font-size : 11.5 px ;
padding : 7 px 8 px ;
}
}
body [ data-ux = "simple" ] . advanced-only { display : none !important }
body [ data-ux = "simple" ] . pro-only-note { display : block }
. pro-only-note { display : none }
. help {
margin-left : 6 px ; width : 18 px ; height : 18 px ; border : 1 px solid var ( - - line - hot ) ; border-radius : 50 % ;
background : #0A0E15 ; color : var ( - - cyan ) ; font : inherit ; font-size : 11 px ; line-height : 16 px ;
display : inline-flex ; align-items : center ; justify-content : center ; cursor : help ; padding : 0 ;
}
. warn b { color : #fff ; font-weight : 700 }
/* ---------- Controls ---------- */
/* ---------- Controls ---------- */
. ctl { display : block ; margin : 15 px 0 0 }
. ctl { display : block ; margin : 15 px 0 0 }
. ctl : first-of-type { margin-top : 0 }
. ctl : first-of-type { margin-top : 0 }
. ctl-top { display : flex ; justify-content : space-between ; align-items : baseline ; gap : 10 px ; margin-bottom : 5 px }
. ctl-top { display : flex ; justify-content : space-between ; align-items : baseline ; gap : 10 px ; margin-bottom : 5 px }
. ctl-l { font-size : 11 px ; letter-spacing : .18 em ; text-transform : uppercase ; color : var ( - - dim ) }
. ctl-l { font-size : 11 px ; letter-spacing : .18 em ; text-transform : uppercase ; color : var ( - - muted ) }
. ctl-v { font-size : 17 px ; color : var ( - - cyan ) ; font-weight : 700 ; text-shadow : 0 0 12 px rgba ( 0 , 240 , 255 , .3 ) ; font-variant-numeric : tabular-nums }
. ctl-v { font-size : 17 px ; color : var ( - - cyan ) ; font-weight : 700 ; text-shadow : 0 0 12 px rgba ( 0 , 240 , 255 , .3 ) ; font-variant-numeric : tabular-nums }
. ctl-v small { color : var ( - - dim ) ; font-size : 11 px ; font-weight : 400 ; letter-spacing : .1 em ; text-shadow : none }
. ctl-v small { color : var ( - - dim ) ; font-size : 11 px ; font-weight : 400 ; letter-spacing : .1 em ; text-shadow : none }
input [ type = range ] {
input [ type = range ] {
@@ -112,7 +158,12 @@ input[type=range]::-moz-range-thumb{
}
}
input [ type = range ] : focus-visible :: -webkit-slider-thumb { outline : 2 px solid var ( - - magenta ) ; outline-offset : 2 px }
input [ type = range ] : focus-visible :: -webkit-slider-thumb { outline : 2 px solid var ( - - magenta ) ; outline-offset : 2 px }
. row { display : grid ; grid-template-columns : 1 fr 1 fr ; gap : 14 px }
. row { display : grid ; grid-template-columns : 1 fr 1 fr ; gap : 14 px }
@ media ( max-width : 520px ) { . row { grid-template-columns : 1 fr } }
@ media ( max-width : 520px ) { . row { grid-template-columns : 1 fr }
body { font-size : 16 px }
. ctl-v { font-size : 19 px }
. chip { font-size : 13 px ; padding : 9 px 12 px }
. panel { padding : 18 px 15 px 20 px }
}
select , input [ type = number ] , input [ type = datetime-local ] {
select , input [ type = number ] , input [ type = datetime-local ] {
font : inherit ; font-size : 14 px ; width : 100 % ;
font : inherit ; font-size : 14 px ; width : 100 % ;
@@ -138,7 +189,7 @@ select:focus,input:focus{outline:2px solid var(--cyan);outline-offset:1px}
. badge {
. badge {
font-size : 10.5 px ; letter-spacing : .12 em ; text-transform : uppercase ;
font-size : 10.5 px ; letter-spacing : .12 em ; text-transform : uppercase ;
border : 1 px solid var ( - - line - hot ) ; border-radius : var ( - - r ) ;
border : 1 px solid var ( - - line - hot ) ; border-radius : var ( - - r ) ;
padding : 4 px 8 px ; color : var ( - - dim ) ; background : #0A0E15
padding : 4 px 8 px ; color : var ( - - muted ) ; background : #0A0E15
}
}
. badge b { color : var ( - - txt ) ; font-weight : 700 }
. badge b { color : var ( - - txt ) ; font-weight : 700 }
. note { font-size : 12 px ; color : var ( - - dim ) ; margin-top : 10 px ; line-height : 1.5 }
. note { font-size : 12 px ; color : var ( - - dim ) ; margin-top : 10 px ; line-height : 1.5 }
@@ -158,8 +209,8 @@ select:focus,input:focus{outline:2px solid var(--cyan);outline-offset:1px}
. sub : first-child { margin-top : 0 }
. sub : first-child { margin-top : 0 }
table { width : 100 % ; border-collapse : collapse }
table { width : 100 % ; border-collapse : collapse }
td { padding : 8 px 0 ; border-bottom : 1 px dotted var ( - - line - hot ) ; vertical-align : baseline }
td { padding : 8 px 0 ; border-bottom : 1 px dotted var ( - - line - hot ) ; vertical-align : baseline }
td . n { text-align : right ; font-variant-numeric : tabular-nums ; font-weight : 700 ; color : #EAF6 FF ; white-space : nowrap }
td . n { text-align : right ; font-variant-numeric : tabular-nums ; font-weight : 700 ; color : #F3FA FF ; white-space : nowrap }
td . p { text-align : right ; width : 62 px ; color : var ( - - dim ) ; font-size : 12 px ; font-variant-numeric : tabular-nums }
td . p { text-align : right ; width : 62 px ; color : var ( - - muted ) ; font-size : 12 px ; font-variant-numeric : tabular-nums }
tr . tot td { border-bottom : 0 ; border-top : 1 px solid var ( - - line - hot ) ; padding-top : 11 px ; color : var ( - - cyan ) }
tr . tot td { border-bottom : 0 ; border-top : 1 px solid var ( - - line - hot ) ; padding-top : 11 px ; color : var ( - - cyan ) }
tr . tot td . n { color : var ( - - cyan ) ; text-shadow : 0 0 12 px rgba ( 0 , 240 , 255 , .3 ) ; font-size : 17 px }
tr . tot td . n { color : var ( - - cyan ) ; text-shadow : 0 0 12 px rgba ( 0 , 240 , 255 , .3 ) ; font-size : 17 px }
. hero {
. hero {
@@ -186,7 +237,7 @@ tr.tot td.n{color:var(--cyan);text-shadow:0 0 12px rgba(0,240,255,.3);font-size:
. tl . t { font-size : 17 px ; font-weight : 700 ; color : var ( - - cyan ) ; font-variant-numeric : tabular-nums ; text-shadow : 0 0 12 px rgba ( 0 , 240 , 255 , .25 ) }
. tl . t { font-size : 17 px ; font-weight : 700 ; color : var ( - - cyan ) ; font-variant-numeric : tabular-nums ; text-shadow : 0 0 12 px rgba ( 0 , 240 , 255 , .25 ) }
. tl . t em { font-style : normal ; font-size : 11 px ; letter-spacing : .14 em ; color : var ( - - dim ) ; margin-left : 8 px ; text-transform : uppercase ; text-shadow : none }
. tl . t em { font-style : normal ; font-size : 11 px ; letter-spacing : .14 em ; color : var ( - - dim ) ; margin-left : 8 px ; text-transform : uppercase ; text-shadow : none }
. tl . a { color : #EAF6FF ; font-size : 14 px ; margin-top : 1 px }
. tl . a { color : #EAF6FF ; font-size : 14 px ; margin-top : 1 px }
. tl . d { color : var ( - - dim ) ; font-size : 12 px ; margin-top : 2 px ; line-height : 1.45 }
. tl . d { color : var ( - - muted ) ; font-size : 12 px ; margin-top : 2 px ; line-height : 1.45 }
. tl li . bake . t { color : var ( - - amber ) ; text-shadow : 0 0 12 px rgba ( 255 , 176 , 0 , .3 ) }
. tl li . bake . t { color : var ( - - amber ) ; text-shadow : 0 0 12 px rgba ( 255 , 176 , 0 , .3 ) }
. countdown {
. countdown {
border : 1 px solid var ( - - magenta ) ; background : rgba ( 255 , 45 , 120 , .07 ) ;
border : 1 px solid var ( - - magenta ) ; background : rgba ( 255 , 45 , 120 , .07 ) ;
@@ -204,7 +255,7 @@ tr.tot td.n{color:var(--cyan);text-shadow:0 0 12px rgba(0,240,255,.3);font-size:
. btn : hover { border-color : var ( - - cyan ) ; color : var ( - - cyan ) }
. btn : hover { border-color : var ( - - cyan ) ; color : var ( - - cyan ) }
. btn . pri { background : var ( - - magenta ) ; border-color : var ( - - magenta ) ; color : #fff ; font-weight : 700 }
. btn . pri { background : var ( - - magenta ) ; border-color : var ( - - magenta ) ; color : #fff ; font-weight : 700 }
. btn . pri : hover { box-shadow : 0 0 20 px rgba ( 255 , 45 , 120 , .5 ) ; color : #fff }
. btn . pri : hover { box-shadow : 0 0 20 px rgba ( 255 , 45 , 120 , .5 ) ; color : #fff }
footer { color : var ( - - dim ) ; font-size : 11.5 px ; line-height : 1.6 ; margin-top : 22 px ; border-top : 1 px solid var ( - - line ) ; padding-top : 16 px }
footer { color : var ( - - muted ) ; font-size : 11.5 px ; line-height : 1.6 ; margin-top : 22 px ; border-top : 1 px solid var ( - - line ) ; padding-top : 16 px }
footer b { color : var ( - - txt ) }
footer b { color : var ( - - txt ) }
@ media ( prefers-reduced-motion : reduce ) { * { animation : none !important ; transition : none !important } }
@ media ( prefers-reduced-motion : reduce ) { * { animation : none !important ; transition : none !important } }
@@ -253,9 +304,25 @@ footer b{color:var(--txt)}
< header >
< header >
< h1 class = "brand" > TEIG< em > //< / em > TERMINAL< / h1 >
< h1 class = "brand" > TEIG< em > //< / em > TERMINAL< / h1 >
< p class = "tag" > Pizzateig-Kalkulator · Bäckerprozent · Vorteig · Rückwärts-Zeitplan< / p >
< p class = "tag" > Pizzateig-Kalkulator · Bäckerprozent · Vorteig · Rückwärts-Zeitplan< / p >
< div class = "status" > < span class = "dot" > < / span > < span id = "statusline" > System bereit< / span > < / div >
< div class = "status" > < span class = "dot" > < / span > < span id = "statusline" > System bereit · v1.1 < / span > < / div >
< / header >
< / header >
< div class = "quick-summary noprint" id = "quickSummary" aria-label = "Kurzüberblick" > < / div >
< section class = "panel noprint" >
< p class = "ph" > < b > 00 // Schnellstart< / b > < span > Einfach oder Profi< / span > < / p >
< div class = "ctl" >
< div class = "ctl-top" > < span class = "ctl-l" > Ansicht< / span > < / div >
< div class = "chips" id = "uxModes" role = "group" aria-label = "Ansicht" > < / div >
< p class = "note" > Einfach fragt nur: Wann soll die Pizza fertig sein, welcher Stil, wie viele Ballen und wie schwer pro Ballen. Profi blendet alle Stellschrauben ein.< / p >
< / div >
< label class = "ctl" >
< div class = "ctl-top" > < span class = "ctl-l" > Pizza fertig am< / span > < / div >
< input type = "datetime-local" id = "bakeAtQuick" >
< / label >
< p class = "note pro-only-note" > Profi-Werte wie Hydration, Gare und Vorteig laufen mit sinnvollen Defaults im Hintergrund. Wechsel auf „Profi“, wenn du sie anpassen möchtest.< / p >
< / section >
<!-- CHARGE -->
<!-- CHARGE -->
< section class = "panel noprint" >
< section class = "panel noprint" >
< p class = "ph" > < b > 01 // Charge< / b > < span id = "doughsum" > —< / span > < / p >
< p class = "ph" > < b > 01 // Charge< / b > < span id = "doughsum" > —< / span > < / p >
@@ -271,7 +338,7 @@ footer b{color:var(--txt)}
< / div >
< / div >
< / div >
< / div >
< div class = "ctl" >
< div class = "ctl advanced-only " >
< div class = "ctl-top" > < span class = "ctl-l" > Größe bestimmen über< / span > < / div >
< div class = "ctl-top" > < span class = "ctl-l" > Größe bestimmen über< / span > < / div >
< div class = "chips" id = "modes" role = "group" aria-label = "Berechnungsart" > < / div >
< div class = "chips" id = "modes" role = "group" aria-label = "Berechnungsart" > < / div >
< / div >
< / div >
@@ -279,18 +346,18 @@ footer b{color:var(--txt)}
< div id = "modeW" >
< div id = "modeW" >
< label class = "ctl" >
< label class = "ctl" >
< div class = "ctl-top" > < span class = "ctl-l" > Gewicht pro Teigling< / span > < span class = "ctl-v" id = "v-ball" > < / span > < / div >
< div class = "ctl-top" > < span class = "ctl-l" > Gewicht pro Teigling< / span > < span class = "ctl-v" id = "v-ball" > < / span > < / div >
< input type = "range" id = "ball" min = "12 0" max = "12 00" step = "5" >
< input type = "range" id = "ball" min = "15 0" max = "4 00" step = "5" >
< / label >
< / label >
< / div >
< / div >
< div id = "modeD" hidden >
< div id = "modeD" class = "advanced-only" hidden >
< label class = "ctl" >
< label class = "ctl" >
< div class = "ctl-top" > < span class = "ctl-l" > Durchmesser< / span > < span class = "ctl-v" id = "v-dia" > < / span > < / div >
< div class = "ctl-top" > < span class = "ctl-l" > Durchmesser< / span > < span class = "ctl-v" id = "v-dia" > < / span > < / div >
< input type = "range" id = "dia" min = "16" max = "45" step = "1" >
< input type = "range" id = "dia" min = "16" max = "45" step = "1" >
< / label >
< / label >
< / div >
< / div >
< div id = "modeP" hidden >
< div id = "modeP" class = "advanced-only" hidden >
< div class = "row" >
< div class = "row" >
< label class = "ctl" >
< label class = "ctl" >
< div class = "ctl-top" > < span class = "ctl-l" > Blech Länge< / span > < span class = "ctl-v" id = "v-panL" > < / span > < / div >
< div class = "ctl-top" > < span class = "ctl-l" > Blech Länge< / span > < span class = "ctl-v" id = "v-panL" > < / span > < / div >
@@ -305,7 +372,7 @@ footer b{color:var(--txt)}
< / section >
< / section >
<!-- MEHL -->
<!-- MEHL -->
< section class = "panel noprint" >
< section class = "panel noprint advanced-only " >
< p class = "ph" > < b > 02 // Mehl< / b > < span id = "flourW" > —< / span > < / p >
< p class = "ph" > < b > 02 // Mehl< / b > < span id = "flourW" > —< / span > < / p >
< select id = "flour" aria-label = "Mehl auswählen" > < / select >
< select id = "flour" aria-label = "Mehl auswählen" > < / select >
< div class = "meta" id = "flourMeta" > < / div >
< div class = "meta" id = "flourMeta" > < / div >
@@ -316,7 +383,7 @@ footer b{color:var(--txt)}
< / section >
< / section >
<!-- REZEPTUR -->
<!-- REZEPTUR -->
< section class = "panel noprint" >
< section class = "panel noprint advanced-only " >
< p class = "ph" > < b > 03 // Rezeptur< / b > < span > Bäckerprozent< / span > < / p >
< p class = "ph" > < b > 03 // Rezeptur< / b > < span > Bäckerprozent< / span > < / p >
< label class = "ctl" >
< label class = "ctl" >
< div class = "ctl-top" > < span class = "ctl-l" > Hydration< / span > < span class = "ctl-v" id = "v-hyd" > < / span > < / div >
< div class = "ctl-top" > < span class = "ctl-l" > Hydration< / span > < span class = "ctl-v" id = "v-hyd" > < / span > < / div >
@@ -343,7 +410,7 @@ footer b{color:var(--txt)}
< / section >
< / section >
<!-- GARE -->
<!-- GARE -->
< section class = "panel noprint" >
< section class = "panel noprint advanced-only " >
< p class = "ph" > < b > 04 // Gare< / b > < span id = "fermsum" > —< / span > < / p >
< p class = "ph" > < b > 04 // Gare< / b > < span id = "fermsum" > —< / span > < / p >
< div class = "ctl" >
< div class = "ctl" >
@@ -385,7 +452,7 @@ footer b{color:var(--txt)}
< / section >
< / section >
<!-- VORTEIG -->
<!-- VORTEIG -->
< section class = "panel noprint" >
< section class = "panel noprint advanced-only " >
< p class = "ph" > < b > 05 // Vorteig< / b > < span id = "pfsum" > —< / span > < / p >
< p class = "ph" > < b > 05 // Vorteig< / b > < span id = "pfsum" > —< / span > < / p >
< div class = "chips" id = "pfTypes" role = "group" aria-label = "Vorteig" > < / div >
< div class = "chips" id = "pfTypes" role = "group" aria-label = "Vorteig" > < / div >
< div id = "pfBox" hidden >
< div id = "pfBox" hidden >
@@ -423,7 +490,7 @@ footer b{color:var(--txt)}
<!-- ZEITPLAN -->
<!-- ZEITPLAN -->
< section class = "panel" >
< section class = "panel" >
< p class = "ph" > < b > 07 // Zeitplan< / b > < span > rückwärts ab Backzeit< / span > < / p >
< p class = "ph" > < b > 07 // Zeitplan< / b > < span > rückwärts ab Backzeit< / span > < / p >
< label class = "ctl noprint" style = "margin-bottom:16px" >
< label class = "ctl noprint advanced-only " style = "margin-bottom:16px" >
< div class = "ctl-top" > < span class = "ctl-l" > Pizza fertig am< / span > < / div >
< div class = "ctl-top" > < span class = "ctl-l" > Pizza fertig am< / span > < / div >
< input type = "datetime-local" id = "bakeAt" >
< input type = "datetime-local" id = "bakeAt" >
< / label >
< / label >
@@ -434,8 +501,8 @@ footer b{color:var(--txt)}
< div id = "printfoot" class = "printonly" > < / div >
< div id = "printfoot" class = "printonly" > < / div >
< div class = "actions noprint" >
< div class = "actions noprint" >
< button type = "button" class = "btn pri" id = "share" > Rezept-Link kopier en< / button >
< button type = "button" class = "btn pri" id = "share" > Rezept teil en< / button >
< button type = "button" class = "btn" id = "pdf" > Als PDF speichern < / button >
< button type = "button" class = "btn" id = "pdf" > Druck/PDF < / button >
< button type = "button" class = "btn" id = "copyTxt" > Als Text kopieren< / button >
< button type = "button" class = "btn" id = "copyTxt" > Als Text kopieren< / button >
< button type = "button" class = "btn" id = "reset" > Zurücksetzen< / button >
< button type = "button" class = "btn" id = "reset" > Zurücksetzen< / button >
< / div >
< / div >
@@ -486,10 +553,10 @@ const FLOURS = [
{ g : "Spezial" , id : "custom" , n : "Eigenes Mehl / Blend" , w : "—" , pr : 0 , hyd : [ 50 , 90 ] , reife : [ 0 , 120 ] , vt : true , note : "Keine Prüfung — du weißt, was du tust." }
{ g : "Spezial" , id : "custom" , n : "Eigenes Mehl / Blend" , w : "—" , pr : 0 , hyd : [ 50 , 90 ] , reife : [ 0 , 120 ] , vt : true , note : "Keine Prüfung — du weißt, was du tust." }
] ;
] ;
/* ---------- Stile (tf = Teigdichte in g/cm²) ---------- */
/* ---------- Stile (tf = Teigdichte in g/cm², yeastK = Frischhefe-Referenz ) ---------- */
const STYLES = {
const STYLES = {
napo : { n : "Napoletana" , ball : 250 , hyd : 62 , salt : 2.8 , oil : 0 , sugar : 0 , tf : 0.36 , mode : "w" } ,
napo : { n : "Napoletana" , ball : 250 , hyd : 62 , salt : 2.8 , oil : 0 , sugar : 0 , tf : 0.36 , mode : "w" , yeastK : 1.5 } ,
contemp : { n : "Contemporanea" , ball : 280 , hyd : 70 , salt : 2.8 , oil : 0 , sugar : 0 , tf : 0.39 , mode : "w" } ,
contemp : { n : "Contemporanea" , ball : 280 , hyd : 70 , salt : 2.8 , oil : 0 , sugar : 0 , tf : 0.39 , mode : "w" , yeastK : 1.7 } ,
ny : { n : "New York" , ball : 300 , hyd : 63 , salt : 2.0 , oil : 2.5 , sugar : 1.5 , tf : 0.31 , mode : "w" } ,
ny : { n : "New York" , ball : 300 , hyd : 63 , salt : 2.0 , oil : 2.5 , sugar : 1.5 , tf : 0.31 , mode : "w" } ,
tonda : { n : "Römisch tonda" , ball : 200 , hyd : 57 , salt : 2.2 , oil : 4 , sugar : 0 , tf : 0.26 , mode : "w" } ,
tonda : { n : "Römisch tonda" , ball : 200 , hyd : 57 , salt : 2.2 , oil : 4 , sugar : 0 , tf : 0.26 , mode : "w" } ,
teglia : { n : "Teglia / Blech" , ball : 800 , hyd : 78 , salt : 2.2 , oil : 2 , sugar : 0 , tf : 0.55 , mode : "p" } ,
teglia : { n : "Teglia / Blech" , ball : 800 , hyd : 78 , salt : 2.2 , oil : 2 , sugar : 0 , tf : 0.55 , mode : "p" } ,
@@ -515,7 +582,7 @@ const SEQ = {
warmcold : { n : "Warm → Kalt" , fridge : true ,
warmcold : { n : "Warm → Kalt" , fridge : true ,
note : "Hybrid: erst Stockgare bei Raumtemperatur, dann in den Kühlschrank, kurz vor dem Backen aufwärmen. Sehr verbreitet, braucht wenig Hefe — aber der Teig kühlt langsam durch und gärt im Kühlschrank wärmer als gedacht." } ,
note : "Hybrid: erst Stockgare bei Raumtemperatur, dann in den Kühlschrank, kurz vor dem Backen aufwärmen. Sehr verbreitet, braucht wenig Hefe — aber der Teig kühlt langsam durch und gärt im Kühlschrank wärmer als gedacht." } ,
coldwarm : { n : "Kalt → Warm" , fridge : true ,
coldwarm : { n : "Kalt → Warm" , fridge : true ,
note : "Lehmann-Methode: d irekt nach dem Kneten kalt stellen, die Gare passiert im Kühlschrank, die warme Zeit danach dient dem Handling. Am besten planbar, braucht deutlich mehr Hefe ." }
note : "D irekt nach dem Kneten kalt stellen, anschließend in warmer Stückgare ausreifen lassen. Gut planbar und besonders für längere Führungen geeignet; die warme Endgare macht die Ballen sichtbar aktiv und dehnbar ." }
} ;
} ;
const STAGLIO = {
const STAGLIO = {
before : "Ballen vor der Kälte" ,
before : "Ballen vor der Kälte" ,
@@ -525,13 +592,14 @@ const STAGLIO = {
const MODES = { w : "Ballgewicht" , d : "Durchmesser" , p : "Blech L× B" } ;
const MODES = { w : "Ballgewicht" , d : "Durchmesser" , p : "Blech L× B" } ;
const YEASTS = { fresh : "Frischhefe" , dry : "Trockenhefe" , sour : "Sauerteig" } ;
const YEASTS = { fresh : "Frischhefe" , dry : "Trockenhefe" , sour : "Sauerteig" } ;
const PFS = { none : "Ohne (Direktteig)" , poolish : "Poolish" , biga : "Biga" } ;
const PFS = { none : "Ohne (Direktteig)" , poolish : "Poolish" , biga : "Biga" } ;
const UX _MODES = { simple : "Einfach" , pro : "Profi" } ;
const PF _HYD = { poolish : 1.0 , biga : 0.45 } ;
const PF _HYD = { poolish : 1.0 , biga : 0.45 } ;
/* ============================================================
/* ============================================================
STATE
STATE
============================================================ */
============================================================ */
const DEFAULT = {
const DEFAULT = {
style : "napo" , flour : "cap-pizzeria" , balls : 4 , mode : "w" ,
style : "napo" , flour : "cap-pizzeria" , balls : 4 , mode : "w" , uxMode : "simple" ,
ball : 250 , dia : 30 , panL : 40 , panW : 30 ,
ball : 250 , dia : 30 , panL : 40 , panW : 30 ,
hyd : 62 , salt : 2.8 , oil : 0 , sugar : 0 , yeast : "fresh" ,
hyd : 62 , salt : 2.8 , oil : 0 , sugar : 0 , yeast : "fresh" ,
plan : "d24" , seq : "warmcold" , staglio : "before" ,
plan : "d24" , seq : "warmcold" , staglio : "before" ,
@@ -540,11 +608,16 @@ const DEFAULT = {
bake : null
bake : null
} ;
} ;
let S = Object . assign ( { } , DEFAULT ) ;
let S = Object . assign ( { } , DEFAULT ) ;
const BALL _MIN = 150 ;
const BALL _MAX = 400 ;
const SIMPLE _PREP _H = 0.75 ; // Kneten + Formen im Zeitplan
const SIMPLE _SAME _DAY _MAX _H = 12 ; // Same-Day im Einfach-Modus immer warm führen
/* ============================================================
/* ============================================================
RECHENKERN
RECHENKERN
============================================================ */
============================================================ */
const clamp = ( x , a , b ) => Math . min ( b , Math . max ( a , x ) ) ;
const clamp = ( x , a , b ) => Math . min ( b , Math . max ( a , x ) ) ;
const clampBall = x => clamp ( Number . isFinite ( + x ) ? + x : DEFAULT . ball , BALL _MIN , BALL _MAX ) ;
const rate = T => Math . pow ( 2.5 , ( T - 20 ) / 10 ) ; // Q10 = 2.5
const rate = T => Math . pow ( 2.5 , ( T - 20 ) / 10 ) ; // Q10 = 2.5
/* Eine Garephase mit Temperaturwechsel. tc = Stunden, bis der Teigkern
/* Eine Garephase mit Temperaturwechsel. tc = Stunden, bis der Teigkern
@@ -597,7 +670,7 @@ function calc(s){
const pfFrac = hasPf ? s . pfPct / 100 : 0 ;
const pfFrac = hasPf ? s . pfPct / 100 : 0 ;
// --- Triebmittel in % vom Mehl ---
// --- Triebmittel in % vom Mehl ---
let fresh = 6 / U ; // Frischhefe-Referenz
let fresh = ( st . yeastK ? ? 6 ) / U ; // stilbezogene Frischhefe-Referenz
fresh *= 1 + Math . max ( 0 , s . salt - 2.5 ) * 0.06 ; // Salz bremst
fresh *= 1 + Math . max ( 0 , s . salt - 2.5 ) * 0.06 ; // Salz bremst
if ( hasPf ) fresh *= ( 1 - 0.8 * pfFrac ) ; // Vorteig bringt schon Trieb
if ( hasPf ) fresh *= ( 1 - 0.8 * pfFrac ) ; // Vorteig bringt schon Trieb
fresh = clamp ( fresh , 0.01 , 6 ) ;
fresh = clamp ( fresh , 0.01 , 6 ) ;
@@ -778,11 +851,43 @@ function applyPlan(){
if ( S . seq === "coldwarm" ) S . roomH = Math . min ( S . roomH , 3 ) ;
if ( S . seq === "coldwarm" ) S . roomH = Math . min ( S . roomH , 3 ) ;
}
}
/* Einfach-Modus: Der Nutzer gibt nur Zielzeit, Stil und Teigmenge vor.
Die Führung wird daraus automatisch gewählt:
- Same-Day: immer warm, ohne Kühlschrank.
- Länger als Same-Day: Lehmann-Methode (Kalt → Warm) als Standard. */
function applySimpleFermentation ( ) {
if ( S . uxMode !== "simple" || ! S . bake ) return ;
const available = ( S . bake - Date . now ( ) ) / 3600e3 ;
const doughH = clamp ( Math . round ( ( available - SIMPLE _PREP _H ) * 2 ) / 2 , 2 , 72 ) ;
S . yeast = "fresh" ;
S . pf = "none" ;
if ( doughH <= SIMPLE _SAME _DAY _MAX _H ) {
S . plan = "same" ;
S . seq = "room" ;
S . staglio = "before" ;
S . fridgeH = 0 ;
S . roomH = doughH ;
return ;
}
S . seq = "coldwarm" ;
S . staglio = "after" ;
S . roomH = 4 ;
S . fridgeH = Math . max ( 4 , Math . round ( doughH - S . roomH ) ) ;
const total = S . roomH + S . fridgeH ;
S . plan = total <= 18 ? "night" : total <= 30 ? "d24" : total <= 54 ? "d48" : "d72" ;
}
function initUI ( ) {
function initUI ( ) {
buildFlourSelect ( ) ;
buildFlourSelect ( ) ;
chips ( $ ( "uxModes" ) , UX _MODES , "uxMode" , k => {
if ( k === "simple" ) S . mode = "w" ;
} ) ;
chips ( $ ( "styles" ) , STYLES , "style" , k => {
chips ( $ ( "styles" ) , STYLES , "style" , k => {
const st = STYLES [ k ] ;
const st = STYLES [ k ] ;
S . ball = st . ball ; S . hyd = st . hyd ; S . salt = st . salt ; S . oil = st . oil ; S . sugar = st . sugar ; S . mode = st . mode ;
S . ball = clampBall ( st . ball ) ; S . hyd = st . hyd ; S . salt = st . salt ; S . oil = st . oil ; S . sugar = st . sugar ; S . mode = st . mode ;
if ( S . uxMode === "simple" ) S . mode = "w" ;
} ) ;
} ) ;
chips ( $ ( "modes" ) , MODES , "mode" ) ;
chips ( $ ( "modes" ) , MODES , "mode" ) ;
chips ( $ ( "yeastTypes" ) , YEASTS , "yeast" ) ;
chips ( $ ( "yeastTypes" ) , YEASTS , "yeast" ) ;
@@ -807,9 +912,11 @@ function initUI(){
} ) ;
} ) ;
$ ( "ballsMinus" ) . onclick = ( ) => { S . balls = Math . max ( 1 , S . balls - 1 ) ; render ( ) ; } ;
$ ( "ballsMinus" ) . onclick = ( ) => { S . balls = Math . max ( 1 , S . balls - 1 ) ; render ( ) ; } ;
$ ( "ballsPlus" ) . onclick = ( ) => { S . balls = Math . min ( 60 , S . balls + 1 ) ; render ( ) ; } ;
$ ( "ballsPlus" ) . onclick = ( ) => { S . balls = Math . min ( 60 , S . balls + 1 ) ; render ( ) ; } ;
$ ( "bakeAt" ) . addEventListener ( "input" , e => {
const bakeInputHandler = e => {
if ( e . target . value ) { S . bake = new Date ( e . target . value ) . getTime ( ) ; render ( ) ; }
if ( e . target . value ) { S . bake = new Date ( e . target . value ) . getTime ( ) ; render ( ) ; }
} ) ;
} ;
$ ( "bakeAt" ) . addEventListener ( "input" , bakeInputHandler ) ;
$ ( "bakeAtQuick" ) . addEventListener ( "input" , bakeInputHandler ) ;
$ ( "applyFlour" ) . onclick = ( ) => {
$ ( "applyFlour" ) . onclick = ( ) => {
const f = FLOURS . find ( x => x . id === S . flour ) ;
const f = FLOURS . find ( x => x . id === S . flour ) ;
@@ -823,16 +930,20 @@ function initUI(){
$ ( "share" ) . onclick = share ;
$ ( "share" ) . onclick = share ;
$ ( "copyTxt" ) . onclick = copyText ;
$ ( "copyTxt" ) . onclick = copyText ;
$ ( "pdf" ) . onclick = savePdf ;
$ ( "pdf" ) . onclick = savePdf ;
$ ( "reset" ) . onclick = ( ) => { S = Object . assign ( { } , DEFAULT ) ; S . bake = defaultBake ( ) ; location . hash = "" ; render ( ) ; } ;
$ ( "reset" ) . onclick = ( ) => { if ( ! confirm ( "Alle Werte zurücksetzen?" ) ) return ; S = Object . assign ( { } , DEFAULT ) ; S . bake = defaultBake ( ) ; location . hash = "" ; render ( ) ; } ;
}
}
function syncInputs ( ) {
function syncInputs ( ) {
document . body . dataset . ux = S . uxMode ;
S . ball = clampBall ( S . ball ) ;
if ( S . uxMode === "simple" && S . mode !== "w" ) S . mode = "w" ;
applySimpleFermentation ( ) ;
RANGES . forEach ( id => { $ ( id ) . value = S [ id ] ; } ) ;
RANGES . forEach ( id => { $ ( id ) . value = S [ id ] ; } ) ;
$ ( "ballsOut" ) . textContent = S . balls ;
$ ( "ballsOut" ) . textContent = S . balls ;
$ ( "flour" ) . value = S . flour ;
$ ( "flour" ) . value = S . flour ;
[ "styles" , "modes" , "yeastTypes" , "pfTypes" , "plans" , "seqs" , "staglios" ] . forEach ( h => {
[ "uxModes" , " styles", "modes" , "yeastTypes" , "pfTypes" , "plans" , "seqs" , "staglios" ] . forEach ( h => {
const key = { styles : "style" , modes : "mode" , yeastTypes : "yeast" , pfTypes : "pf" , plans : "plan" , seqs : "seq" , staglios : "staglio" } [ h ] ;
const key = { uxModes : "uxMode" , styles : "style" , modes : "mode" , yeastTypes : "yeast" , pfTypes : "pf" , plans : "plan" , seqs : "seq" , staglios : "staglio" } [ h ] ;
const keys = Object . keys ( { styles : STYLES , modes : MODES , yeastTypes : YEASTS , pfTypes : PFS , plans : PLANS , seqs : SEQ , staglios : STAGLIO } [ h ] ) ;
const keys = Object . keys ( { uxModes : UX _MODES , styles : STYLES , modes : MODES , yeastTypes : YEASTS , pfTypes : PFS , plans : PLANS , seqs : SEQ , staglios : STAGLIO } [ h ] ) ;
[ ... $ ( h ) . children ] . forEach ( ( b , i ) => b . setAttribute ( "aria-pressed" , S [ key ] === keys [ i ] ) ) ;
[ ... $ ( h ) . children ] . forEach ( ( b , i ) => b . setAttribute ( "aria-pressed" , S [ key ] === keys [ i ] ) ) ;
} ) ;
} ) ;
$ ( "seqNote" ) . textContent = SEQ [ S . seq ] . note ;
$ ( "seqNote" ) . textContent = SEQ [ S . seq ] . note ;
@@ -859,7 +970,9 @@ function syncInputs(){
$ ( "v-pfT" ) . innerHTML = n1 ( S . pfT ) + " <small>°C</small>" ;
$ ( "v-pfT" ) . innerHTML = n1 ( S . pfT ) + " <small>°C</small>" ;
const d = new Date ( S . bake ) ;
const d = new Date ( S . bake ) ;
$ ( "bakeAt" ) . value = new Date ( d . getTime ( ) - d . getTimezoneOffset ( ) * 60e3 ) . toISOString ( ) . slice ( 0 , 16 ) ;
const bakeVal = new Date ( d . getTime ( ) - d . getTimezoneOffset ( ) * 60e3 ) . toISOString ( ) . slice ( 0 , 16 ) ;
$ ( "bakeAt" ) . value = bakeVal ;
$ ( "bakeAtQuick" ) . value = bakeVal ;
}
}
function renderFlour ( r ) {
function renderFlour ( r ) {
@@ -880,7 +993,7 @@ function renderWarnings(f,r){
const w = [ ] ;
const w = [ ] ;
if ( S . flour !== "custom" ) {
if ( S . flour !== "custom" ) {
if ( r . U > f . reife [ 1 ] * 1.15 )
if ( r . U > f . reife [ 1 ] * 1.15 )
w . push ( [ "bad" , ` Dein Plan entspricht rund ${ Math . round ( r . U ) } h Reife bei 20 °C. ${ f . n } trägt etwa ${ f . reife [ 1 ] } h — der Teig baut ab und wird schlaff. Kürzer führ en oder ein backstärkeres Mehl nehm en. ` ] ) ;
w . push ( [ "bad" , ` <b>Zu lange Gare für dieses Mehl.</b><br> Dein Plan entspricht rund ${ Math . round ( r . U ) } h Reife bei 20 °C. ${ f . n } trägt etwa ${ f . reife [ 1 ] } h. Empfehlung: kürzer führen, Kühlschrank kälter stell en oder ein backstärkeres Mehl wähl en. ` ] ) ;
else if ( r . U < f . reife [ 0 ] * 0.6 )
else if ( r . U < f . reife [ 0 ] * 0.6 )
w . push ( [ "" , ` Nur rund ${ Math . round ( r . U ) } h Reifeäquivalent. ${ f . n } kann deutlich mehr — mit längerer Gare wird der Teig aromatischer und bekömmlicher. ` ] ) ;
w . push ( [ "" , ` Nur rund ${ Math . round ( r . U ) } h Reifeäquivalent. ${ f . n } kann deutlich mehr — mit längerer Gare wird der Teig aromatischer und bekömmlicher. ` ] ) ;
if ( S . hyd > f . hyd [ 1 ] + 3 )
if ( S . hyd > f . hyd [ 1 ] + 3 )
@@ -890,6 +1003,8 @@ function renderWarnings(f,r){
if ( r . hasPf && ! f . vt )
if ( r . hasPf && ! f . vt )
w . push ( [ "" , ` ${ f . n } ist für Vorteige eher schwach. Poolish und Biga verlangen mehr Backstärke. ` ] ) ;
w . push ( [ "" , ` ${ f . n } ist für Vorteige eher schwach. Poolish und Biga verlangen mehr Backstärke. ` ] ) ;
}
}
if ( ( S . style === "napo" || S . style === "contemp" ) && S . seq === "room" && S . roomH < 4 )
w . push ( [ "" , ` <b>Schnellteig:</b> Unter 4 h braucht neapolitanischer Teig vergleichsweise viel Hefe und entwickelt weniger Aroma. Wenn möglich, mindestens 4– 8 h einplanen. ` ] ) ;
if ( S . fridgeT < 4 ) w . push ( [ "" , ` Unter 4 °C stellt die Hefe die Arbeit fast ein. 5– 6 °C sind für die Kühlschrankgare der bessere Bereich. ` ] ) ;
if ( S . fridgeT < 4 ) w . push ( [ "" , ` Unter 4 °C stellt die Hefe die Arbeit fast ein. 5– 6 °C sind für die Kühlschrankgare der bessere Bereich. ` ] ) ;
if ( S . seq === "warmcold" && S . staglio === "after" && S . fridgeH > 0 && r . tcCold > S . fridgeH * 0.4 )
if ( S . seq === "warmcold" && S . staglio === "after" && S . fridgeH > 0 && r . tcCold > S . fridgeH * 0.4 )
w . push ( [ "" , ` Die Masse braucht rund ${ Math . round ( r . tcCold ) } h zum Durchkühlen — bei ${ S . fridgeH } h Kaltgare gärt der Kern also die halbe Zeit deutlich wärmer als ${ n1 ( S . fridgeT ) } °C. Teig flach drücken oder auf mehrere Behälter aufteilen, sonst wird das Ergebnis schwer wiederholbar. ` ] ) ;
w . push ( [ "" , ` Die Masse braucht rund ${ Math . round ( r . tcCold ) } h zum Durchkühlen — bei ${ S . fridgeH } h Kaltgare gärt der Kern also die halbe Zeit deutlich wärmer als ${ n1 ( S . fridgeT ) } °C. Teig flach drücken oder auf mehrere Behälter aufteilen, sonst wird das Ergebnis schwer wiederholbar. ` ] ) ;
@@ -897,7 +1012,8 @@ function renderWarnings(f,r){
w . push ( [ "" , ` Bei der Lehmann-Methode ist die Gare im Kühlschrank abgeschlossen. ${ n1 ( S . roomH ) } h warm danach sind viel — der Teig übergärt leicht. 0,5 bis 2 h reichen, um ihn dehnbar zu bekommen. ` ] ) ;
w . push ( [ "" , ` Bei der Lehmann-Methode ist die Gare im Kühlschrank abgeschlossen. ${ n1 ( S . roomH ) } h warm danach sind viel — der Teig übergärt leicht. 0,5 bis 2 h reichen, um ihn dehnbar zu bekommen. ` ] ) ;
if ( S . seq === "coldwarm" && S . roomH < 0.5 )
if ( S . seq === "coldwarm" && S . roomH < 0.5 )
w . push ( [ "" , ` Ohne Aufwärmzeit ist der Teig sehr elastisch und wehrt sich beim Öffnen. Ab etwa 10 °C Kerntemperatur wird er dehnbar. ` ] ) ;
w . push ( [ "" , ` Ohne Aufwärmzeit ist der Teig sehr elastisch und wehrt sich beim Öffnen. Ab etwa 10 °C Kerntemperatur wird er dehnbar. ` ] ) ;
if ( ! r . isSour && r . yPct < 0.03 ) w . push ( [ "" , ` Die Hefemenge ist rechnerisch winzig. Ohne Feinwaage mit 0,01 g Auflösung wird das Raten — kürzere Gare oder Vorteig sind praktikabler. ` ] ) ;
const yeastG = r . hasPf ? r . pf . yeast + r . main . yeast : r . main . yeast ;
if ( ! r . isSour && yeastG < 0.5 ) w . push ( [ "" , ` Nur ${ yeastG . toFixed ( 2 ) . replace ( "." , "," ) } g Hefe: Für reproduzierbare Ergebnisse eine Feinwaage mit 0,01 g Auflösung verwenden oder die Hefe in Wasser lösen und anteilig dosieren. ` ] ) ;
if ( ! r . isSour && r . yPct > 3 ) w . push ( [ "" , ` Sehr viel Hefe. Der Teig geht schnell, aber flach im Geschmack. Mehr Zeit gibt mehr Aroma. ` ] ) ;
if ( ! r . isSour && r . yPct > 3 ) w . push ( [ "" , ` Sehr viel Hefe. Der Teig geht schnell, aber flach im Geschmack. Mehr Zeit gibt mehr Aroma. ` ] ) ;
if ( r . hasPf && r . main . water < 0 )
if ( r . hasPf && r . main . water < 0 )
w . push ( [ "bad" , ` Der Vorteig enthält mehr Wasser als das ganze Rezept hergibt. Vorteig-Anteil senken oder Hydration erhöhen. ` ] ) ;
w . push ( [ "bad" , ` Der Vorteig enthält mehr Wasser als das ganze Rezept hergibt. Vorteig-Anteil senken oder Hydration erhöhen. ` ] ) ;
@@ -970,6 +1086,20 @@ function renderTimeline(r){
return tl ;
return tl ;
}
}
function renderQuickSummary ( r ) {
const tl = lastTl || timeline ( S , r ) ;
const start = tl . start ;
const items = [
` <span><b> ${ STYLES [ S . style ] . n } </b></span> ` ,
` <span><b> ${ S . balls } × ${ Math . round ( r . ballW ) } g</b> Teiglinge</span> ` ,
` <span>Backen <b> ${ fmtT ( new Date ( S . bake ) ) } ${ fmtDay ( new Date ( S . bake ) ) } </b></span> ` ,
` <span>Start <b> ${ fmtT ( start ) } ${ fmtDay ( start ) } </b></span> `
] ;
if ( S . uxMode !== "simple" ) items . splice ( 2 , 0 , ` <span><b> ${ Math . round ( r . flour ) } g</b> Mehl</span> ` ) ;
$ ( "quickSummary" ) . innerHTML = items . join ( "" ) ;
}
/* ============================================================
/* ============================================================
RENDER
RENDER
============================================================ */
============================================================ */
@@ -982,6 +1112,7 @@ function render(){
renderWarnings ( f , r ) ;
renderWarnings ( f , r ) ;
renderRecipe ( r ) ;
renderRecipe ( r ) ;
lastTl = renderTimeline ( r ) ;
lastTl = renderTimeline ( r ) ;
renderQuickSummary ( r ) ;
renderPrint ( r ) ;
renderPrint ( r ) ;
$ ( "doughsum" ) . textContent = ` ${ S . balls } × ${ Math . round ( r . ballW ) } g = ${ Math . round ( r . total ) } g ` ;
$ ( "doughsum" ) . textContent = ` ${ S . balls } × ${ Math . round ( r . ballW ) } g = ${ Math . round ( r . total ) } g ` ;
@@ -994,13 +1125,14 @@ function render(){
/* ============================================================
/* ============================================================
URL-SHARING
URL-SHARING
============================================================ */
============================================================ */
const KEYS = [ "style" , "flour" , "balls" , "mode" , "ball" , "dia" , "panL" , "panW" , "hyd" , "salt" , "oil" , "sugar" , "yeast" , "plan" , "seq" , "staglio" , "roomH" , "roomT" , "fridgeH" , "fridgeT" , "pf" , "pfPct" , "pfH" , "pfT" ] ;
const KEYS = [ "style" , "flour" , "balls" , "mode" , "uxMode" , " ball", "dia" , "panL" , "panW" , "hyd" , "salt" , "oil" , "sugar" , "yeast" , "plan" , "seq" , "staglio" , "roomH" , "roomT" , "fridgeH" , "fridgeT" , "pf" , "pfPct" , "pfH" , "pfT" ] ;
let hashLock = false ;
let hashLock = false ;
function writeHash ( ) {
function writeHash ( ) {
const p = new URLSearchParams ( ) ;
const p = new URLSearchParams ( ) ;
KEYS . forEach ( k => { if ( S [ k ] !== DEFAULT [ k ] ) p . set ( k , S [ k ] ) ; } ) ;
KEYS . forEach ( k => { if ( S [ k ] !== DEFAULT [ k ] ) p . set ( k , S [ k ] ) ; } ) ;
hashLock = true ;
hashLock = true ;
history . replaceState ( null , "" , p . toString ( ) ? "#" + p . toString ( ) : location . pathname ) ;
const baseUrl = window . location . pathname + window . location . search ;
history . replaceState ( null , "" , p . toString ( ) ? "#" + p . toString ( ) : baseUrl ) ;
setTimeout ( ( ) => hashLock = false , 0 ) ;
setTimeout ( ( ) => hashLock = false , 0 ) ;
}
}
function readHash ( ) {
function readHash ( ) {
@@ -1012,6 +1144,8 @@ function readHash(){
S [ k ] = ( typeof DEFAULT [ k ] === "number" ) ? parseFloat ( v ) : v ;
S [ k ] = ( typeof DEFAULT [ k ] === "number" ) ? parseFloat ( v ) : v ;
} ) ;
} ) ;
if ( ! STYLES [ S . style ] ) S . style = "napo" ;
if ( ! STYLES [ S . style ] ) S . style = "napo" ;
if ( ! UX _MODES [ S . uxMode ] ) S . uxMode = "simple" ;
S . ball = clampBall ( S . ball ) ;
if ( ! PLANS [ S . plan ] ) S . plan = "free" ;
if ( ! PLANS [ S . plan ] ) S . plan = "free" ;
if ( p . has ( "method" ) ) { // alte Links: method=room|cold
if ( p . has ( "method" ) ) { // alte Links: method=room|cold
S . seq = p . get ( "method" ) === "room" ? "room" : "warmcold" ;
S . seq = p . get ( "method" ) === "room" ? "room" : "warmcold" ;
@@ -1102,6 +1236,15 @@ function copyText(){
window . addEventListener ( "hashchange" , ( ) => { if ( ! hashLock ) { readHash ( ) ; render ( ) ; } } ) ;
window . addEventListener ( "hashchange" , ( ) => { if ( ! hashLock ) { readHash ( ) ; render ( ) ; } } ) ;
setInterval ( ( ) => { if ( lastR ) renderTimeline ( lastR ) ; } , 30000 ) ;
setInterval ( ( ) => { if ( lastR ) renderTimeline ( lastR ) ; } , 30000 ) ;
/* iOS Safari zoomt sonst bei schnellem Doppeltippen auf Buttons/Slider.
Pinch-Zoom bleibt unberührt; verhindert wird nur der Double-Tap-Default. */
let lastTouchEnd = 0 ;
document . addEventListener ( "touchend" , e => {
const now = Date . now ( ) ;
if ( now - lastTouchEnd <= 320 ) e . preventDefault ( ) ;
lastTouchEnd = now ;
} , { passive : false } ) ;
/* ---------- Start ---------- */
/* ---------- Start ---------- */
readHash ( ) ;
readHash ( ) ;
initUI ( ) ;
initUI ( ) ;