{"id":6898,"date":"2025-12-23T08:33:20","date_gmt":"2025-12-23T07:33:20","guid":{"rendered":"https:\/\/mildasberg.se\/?page_id=6898"},"modified":"2026-05-07T10:36:35","modified_gmt":"2026-05-07T08:36:35","slug":"pa-gang","status":"publish","type":"page","link":"https:\/\/mildasberg.se\/index.php\/mildasbryggarna\/pa-gang\/","title":{"rendered":"P\u00e5 g\u00e5ng"},"content":{"rendered":"\n<div class=\"wp-block-group has-global-padding is-layout-constrained wp-block-group-is-layout-constrained\">\n<div class=\"wp-block-cover alignwide is-light has-custom-content-position is-position-top-center is-style-default\" style=\"min-height:175px;aspect-ratio:unset;\"><img loading=\"lazy\" decoding=\"async\" width=\"768\" height=\"1024\" class=\"wp-block-cover__image-background wp-image-6901 size-large\" alt=\"Planering\" src=\"https:\/\/mildasberg.se\/wp-content\/uploads\/2025\/12\/Grainfather-768x1024.jpg\" data-object-fit=\"cover\"\/><span aria-hidden=\"true\" class=\"wp-block-cover__background has-background-dim-0 has-background-dim\" style=\"background-color:#8d827e\"><\/span><div class=\"wp-block-cover__inner-container is-layout-flow wp-block-cover-is-layout-flow\">\n<h1 class=\"wp-block-heading has-text-align-center MildasOp50 has-base-color has-qua-background-color has-text-color has-background has-x-large-font-size aploanim fadein \" style=\"margin-top:0px;margin-right:0px;margin-bottom:0px;margin-left:0px;padding-top:var(--wp--preset--spacing--20);padding-right:var(--wp--preset--spacing--20);padding-bottom:var(--wp--preset--spacing--20);padding-left:var(--wp--preset--spacing--20);font-style:normal;font-weight:700;text-transform:capitalize\">P\u00e5 g\u00e5ng hos MildasBryggarna<\/h1>\n<\/div><\/div>\n<\/div>\n\n\n<div class=\"breadcrumbs align wp-block-bcn-breadcrumb-trail has-text-color has-background\" vocab=\"https:\/\/schema.org\/\" typeof=\"BreadcrumbList\">\n\t<span><\/span>\n\t<span property=\"itemListElement\" typeof=\"ListItem\"><a property=\"item\" typeof=\"WebPage\" title=\"G\u00e5 till startsidan.\" href=\"https:\/\/mildasberg.se\" class=\"home\" aria-current=\"page\"><span property=\"name\">Mildasberg<\/span><\/a><meta property=\"position\" content=\"1\"><\/span><\/div>\n\n\n\n<div id=\"section-gdca7e0\" class=\"wp-block-gutentor-e6 section-gdca7e0 gutentor-element gutentor-element-image gutentor-enabled-width\"><div class=\"gutentor-element-image-box\"><div class=\"gutentor-image-thumb\"><img decoding=\"async\" class=\"normal-image\" src=\"https:\/\/mildasberg.se\/wp-content\/uploads\/2024\/06\/MildasBryggarna-Logo-frilagd-2.png\" alt=\"MildasBryggarna frilagd\"\/><\/div><\/div><\/div>\n\n\n\n<p class=\"wp-block-paragraph\"><strong><em>H\u00e4r \u00e4r v\u00e5r tavla \u00f6ver planerade och p\u00e5g\u00e5ende projekt. Vi sk\u00f6ter all dokumentation och planering i <a href=\"https:\/\/brewfather.app\/\">Brewfather<\/a> och via Brewfathers <a href=\"https:\/\/docs.brewfather.app\/api\" target=\"_blank\" rel=\"noreferrer noopener\">API<\/a> har vi m\u00f6jlighet att direkt uppdatera v\u00e5r webbplats med aktuell info. Vi anv\u00e4nder \u00e4ven ett egenytvecklat centralt bryggsystem som \u00f6vervakar och styr v\u00e5ra bryggenheter.<\/em><\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ett API var en av de saker som v\u00e4gde tungt i valet av brygghj\u00e4lpmedel. Lasse gillar att pilla med s\u00e5nt och det \u00e4r en del av hobbyn. Denna version \u00e4r den f\u00f6rsta och vi har l\u00e4rt oss en hel del av m\u00f6jligheterna. Mest arbete lades till att skapa bilderna f\u00f6r j\u00e4skurvan.<br><br>Lasse har jobbat h\u00e5rt med att utveckla ett processbaserat centralt bryggerisystem som p\u00e5 sikt kommer styra och hantera alla v\u00e5ra bryggprylar. Det f\u00f6rsta vi byggt \u00e4r en koppling mot bryggverket s\u00e5 att vi h\u00e4r kan visa vad som sker live n\u00e4r vi brygger \u00f6l. N\u00f6dv\u00e4ndigt? Nja&#8230; Kul? Absolut!<\/p>\n\n\n\n<h2 class=\"wp-block-heading has-large-font-size\">Planerade bryggningar<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">H\u00e4r listas de bryggningar vi har p\u00e5 g\u00e5ng hos MildasBryggarna<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><figure class=\"wp-block-table is-style-regular MildasPlannedBeerTable\"><table><tbody><tr><td><strong>Batchnummer<\/strong><\/td><td><strong>Bryggning<\/strong><\/td><td><strong>Planerat bryggdatum<\/strong><\/td><\/tr><tr><td>404-000-00-01<\/td><td>API-utveckling<\/td><td>2026-04-13<\/td><\/tr><\/tbody><\/table><\/figure><\/p>\n\n\n\n<h2 class=\"wp-block-heading has-large-font-size\">P\u00e5g\u00e5ende bryggningar<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">P\u00e5 bryggdagen visas den bryggning som just nu kokas.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><div id=\"brew-fallback\"><p><b><i>Inga p\u00e5g\u00e5ende bryggningar f\u00f6r n\u00e4rvarande<\/i><\/b><\/p><\/div><div id = \"brew-live\" style = \"display:none;\">  \r\n\t<style> \r\n\t \/* --- Layout --- *\/\r\n\t\t.brew-header {\r\n\t\tdisplay: flex;\r\n\t\tjustify-content: space-between;\r\n\t\talign-items: center;\r\n\t\tmargin-bottom: 1rem;\r\n\t}\r\n\t.brew-title h2 {\r\n\t\t margin: 0;\r\n\t\t font-size: 1.4rem;\r\n\t}\r\n\t#brew-style {\r\n\t\t font-size: 0.85rem;\r\n\t\t opacity: 0.6;\r\n\t}\r\n\t.brew-status {\r\n\t\t display: flex;\r\n\t\t align-items: center;\r\n\t\t gap: 0.4rem;\r\n\t\t font-size: 0.85rem;\r\n\t}\r\n\t.status-dot {\r\n\t\t width: 10px;\r\n\t\t height: 10px;\r\n\t\t border-radius: 50% ;\r\n\t\t background: #666;\r\n\t}\r\n\t.brew-status.online .status-dot {\r\n\t\t background: #3ddc84;\r\n\t\t box-shadow: 0 0 6px rgba(61, 220, 132, 0.7);\r\n\t}\r\n\t.brew-status.offline .status-dot {\r\n\t\t background: #aaa;\r\n\t}\r\n\t.brew-status.lost .status-dot {\r\n\t\t background: #ff5c5c;\r\n\t\t box-shadow: 0 0 6px rgba(255, 92, 92, 0.7);\r\n\t}\r\n\t.brew-main {\r\n\t\t display: flex;\r\n\t\t gap: 1rem;\r\n\t\t margin: 1rem 0;\r\n\t\t align-items: stretch;\r\n\t}\r\n\t.left-column {\r\n\t\t flex: 1 1 auto;\r\n\t\t min-width: 0;\r\n\t\t display: flex;\r\n\t}\r\n\t.left-column .brew-card.temp {\r\n\t\t flex: 1;\r\n\t}\r\n\t.right-column {\r\n\t\tflex: 0 0 220px; \/* \u2190 fast kolumn *\/ \r\n\t\tmax-width: 150px; \/* \u2190 STOPPAR tillv\u00e4xt *\/ \r\n\t\tmin-width: 100px; \/* \u2190 till\u00e5ter krympning *\/ \r\n\t\tdisplay: flex;\r\n\t\tflex-direction: column;\r\n\t\tgap: 1rem;\r\n\t}\r\n\t.right-column .brew-card {\r\n\t\t min-width: 0;\r\n\t}\r\n\t.right-column .brew-card:first-child {\r\n\t\t flex: 1;\r\n\t}\r\n\t.brew-card {\r\n\t\t background: #1c1c1c;\r\n\t\t color: #eee;\r\n\t\t padding: 0rem;\r\n\t\t border-radius: 12px;\r\n\t\t display: flex;\r\n\t\t flex-direction: column;\r\n\t\t justify-content: flex-start;\r\n\t\t text-align: center;\r\n\t}\r\n\t.brew-card h3 {\r\n\t\t display: flex;\r\n\t\t align-items: center;\r\n\t\t justify-content: center;\r\n\t\t margin: 0.3rem 0 0.2rem 0;\r\n\t\t font-size: clamp(1.1rem, 0.9rem + 0.8vw, 1.8rem);\r\n\t\t white-space: normal;\r\n\t\t line-height: 1.2;\r\n\t\t gap: 0.4rem;\r\n\t}\r\n\t.brew-step h3.timer-active {\r\n\t\t letter-spacing: 0.5px;\r\n\t}\r\n\t.brew-step h3.manual-active {\r\n\t  color: #ff9800;\r\n\t  animation: manualPulse 2s ease-in-out infinite;\r\n\t}\r\n\t@keyframes manualPulse {\r\n\t  0%   { opacity: 0.7; }\r\n\t  50%  { opacity: 1; }\r\n\t  100% { opacity: 0.7; }\r\n\t}\r\n\r\n\t.brew-card.temp {\r\n\t\t justify-content: space-between;\r\n\t\t min-width: 0;\r\n\t}\r\n\t.brew-card.heat, \r\n\t.brew-card.pump {\r\n\t\t justify-content: flex-start;\r\n\t} \r\n\r\n\t\/* Gauge container *\/\r\n\t#temp-gauge {\r\n\t\twidth: 100% ;\r\n\t\taspect-ratio: 1 \/ 0.6; \/* styr formen *\/ \r\n\t\tmin-height: 180px; \/* s\u00e4kerst\u00e4ller initial render *\/\r\n\t}\r\n\t#temp-gauge canvas {\r\n\t\t width: 100% !important;\r\n\t\t height: 100% !important;\r\n\t\t display: block;\r\n\t} \r\n\t\t\r\n\t#temp-gauge.stable canvas {\r\n\t  animation: breathe 2.5s ease-in-out infinite;\r\n\t}\r\n\r\n\t@keyframes breathe {\r\n\t  0%   { opacity: 0.85; transform: scale(1); }\r\n\t  50%  { opacity: 1; transform: scale(1.02); }\r\n\t  100% { opacity: 0.85; transform: scale(1); }\r\n\t}\r\n\r\n\t\/* Default: heater OFF *\/\r\n\t.brew-card.heat .heat-icon {\r\n\t\t font-size: 2rem;\r\n\t\t filter: grayscale(100% );\r\n\t\t opacity: 0.4;\r\n\t\t transition: opacity 0.3s ease,\r\n\t\t filter 0.3s ease;\r\n\t} \r\n\r\n\t\/* Heater ON *\/\r\n\t .brew-card.heat.heat-on .heat-icon {\r\n\t\t filter: none;\r\n\t\t opacity: 1;\r\n\t\t animation: heatPulse 1.4s infinite ease-in-out;\r\n\t}\r\n\r\n\t@keyframes heatPulse {\r\n\t\t 0% {\r\n\t\t   transform: scale(1);\r\n\t\t   filter: drop-shadow(0 0 2px rgba(255, 120, 0, 0.4));\r\n\t\t }\r\n\t\t 50% {\r\n\t\t   transform: scale(1.08);\r\n\t\t   filter: drop-shadow(0 0 8px rgba(255, 140, 0, 0.9));\r\n\t\t }\r\n\t\t 100% {\r\n\t\t   transform: scale(1);\r\n\t\t   filter: drop-shadow(0 0 2px rgba(255, 120, 0, 0.4));\r\n\t\t }\r\n\t}\r\n\r\n\t.brew-card.heat.heat-low .heat-icon {\r\n\t\t animation-duration: 2.5s;\r\n\t\t filter: drop-shadow(0 0 4px rgba(255, 120, 0, 0.4));\r\n\t}\r\n\t.brew-card.heat.heat-high .heat-icon {\r\n\t\t animation-duration: 0.9s;\r\n\t\t filter: drop-shadow(0 0 12px rgba(255, 90, 0, 1));\r\n\t} \r\n\r\n\t\/* Heat-bar *\/\r\n\t.heat-bar {\r\n\t\tposition: relative;\r\n\t\tflex: 1 1 auto; \/* \u2190 NYCKELN *\/ \r\n\t\twidth: 40px;\r\n\t\tbackground: #333;\r\n\t\tborder-radius: 6px;\r\n\t\tmargin: 0.3rem auto;\r\n\t\toverflow: hidden;\r\n\t}\r\n\t.heat-fill {\r\n\t\t position: absolute;\r\n\t\t bottom: 0;\r\n\t\t width: 100% ;\r\n\t\t background: linear-gradient(#ffb347, #ff3b3b);\r\n\t\t transition: height 0.5s;\r\n\t}\r\n\t.heat-value {\r\n\t\t margin-bottom: 0.4rem;\r\n\t} \r\n\t\r\n\t\/* Stage Header *\/\r\n\t.brew-step-header {\r\n\t  display: flex;\r\n\t  align-items: center;\r\n\t  justify-content: space-between;\r\n\t}\r\n\r\n\t.stage-timer {\r\n\t  position: relative;\r\n\t  width: 42px;\r\n\t  height: 42px;\r\n\t  flex-shrink: 0;\r\n\t}\r\n\r\n\t.stage-timer .ring {\r\n\t  transform: rotate(-90deg);\r\n\t}\r\n\r\n\t.stage-timer circle {\r\n\t  fill: none;\r\n\t  stroke-width: 4;\r\n\t}\r\n\r\n\t.stage-timer .bg {\r\n\t  stroke: rgba(255,255,255,0.15);\r\n\t}\r\n\r\n\t.stage-timer .progress {\r\n\t  stroke: #4fc3f7;\r\n\t  stroke-linecap: round;\r\n\t  stroke-dasharray: 113; \/* 2\u03c0r \u2248 113 *\/\r\n\t  stroke-dashoffset: 113;\r\n\t  transition: stroke-dashoffset 0.5s linear, stroke 0.3s;\r\n\t}\r\n\r\n\t.stage-timer .time {\r\n\t  position: absolute;\r\n\t  top: 50%;\r\n\t  left: 50%;\r\n\t  transform: translate(-50%, -50%);\r\n\t  font-size: 11px;\r\n\t  font-weight: 600;\r\n\t}\r\n\r\n\t\/* \ud83d\udd25 sista sekunder *\/\r\n\t.stage-timer.warning .progress {\r\n\t  stroke: #ff9800;\r\n\t}\r\n\r\n\t.stage-timer.critical .progress {\r\n\t  stroke: #f44336;\r\n\t  animation: pulse 1s infinite;\r\n\t}\r\n\r\n\t@keyframes pulse {\r\n\t  0%,100% { transform: scale(1) rotate(-90deg); }\r\n\t  50% { transform: scale(1.08) rotate(-90deg); }\r\n}\r\n\t\/* Manual header symbol *\/\r\n\t.stage-manual-icon {\r\n\t  display: none;\r\n\t  margin-left: 6px;\r\n\t  color: #ff9800;\r\n\t  animation: manualPulse 2s ease-in-out infinite;\r\n\t}\r\n\r\n\t.stage-manual-icon.active {\r\n\t  display: inline;\r\n\t}\r\n\r\n\t@keyframes manualPulse {\r\n\t  0%   { opacity: 0.6; transform: scale(1); }\r\n\t  50%  { opacity: 1; transform: scale(1.15); }\r\n\t  100% { opacity: 0.6; transform: scale(1); }\r\n\t}\r\n\t\t\r\n\t\/* Pump *\/\r\n\t.pump-indicator {\r\n\t\t display: flex;\r\n\t\t flex-direction: column;\r\n\t\t align-items: center;\r\n\t\t gap: 0.3rem;\r\n\t}\r\n\t.pump-icon {\r\n\t\t font-size: 3rem;\r\n\t}\r\n\t.pump-indicator.running .pump-icon {\r\n\t\t animation: spin 1.8s linear infinite;\r\n\t\t color: #4fc3f7;\r\n\t}\r\n\t.pump-indicator.stopped .pump-icon {\r\n\t\t color: #666;\r\n\t}\r\n\r\n\t@keyframes spin {\r\n\t\t from {\r\n\t\t   transform: rotate(0deg);\r\n\t\t }\r\n\t\t to {\r\n\t\t   transform: rotate(360deg);\r\n\t\t }\r\n\r\n\t} \/* Progress bars *\/\r\n\t.progress-bar {\r\n\t\t height: 14px;\r\n\t\t background: #333;\r\n\t\t border-radius: 7px;\r\n\t\t overflow: hidden;\r\n\t\t margin: 0.5rem 0;\r\n\t}\r\n\t.progress-fill {\r\n\t\t height: 100% ;\r\n\t\t background: linear-gradient(to right, #4caf50, #81c784);\r\n\t\t transition: width 0.5s,\r\n\t\t background 0.3s;\r\n\t}\r\n\t.progress-meta {\r\n\t\t display: flex;\r\n\t\t justify-content: space-between;\r\n\t\t font-size: 0.85rem;\r\n\t\t color: #aaa;\r\n\t}\r\n\t.progress-fill.running {\r\n\t\t background: linear-gradient(to right, #4caf50, #81c784);\r\n\t\t animation: breathe 3.5s ease-in-out infinite;\r\n\t}\r\n\t.progress-fill.timed {\r\n\t\t background: linear-gradient(to right, #2196f3, #64b5f6);\r\n\t}\r\n\t.progress-fill.manual {\r\n\t\t background: linear-gradient(to right, #ff9800, #ffb74d);\r\n\t}\r\n\t.progress-fill.done {\r\n\t\t background: linear-gradient(to right, #2196f3, #64b5f6)\r\n\t}\r\n\t.progress-fill.total {\r\n\t\t background: linear-gradient(to right, #4caf50, #81c784);\r\n\t}\r\n\t.progress-fill.total.manual {\r\n\t\t background: linear-gradient(to right, #ff9800, #ffb74d);\r\n\t}\r\n\t.progress-fill.total.timed {\r\n\t\t background: linear-gradient(to right, #2196f3, #64b5f6);\r\n\t}\r\n\t @keyframes breathe {\r\n\t\t 0% {\r\n\t\t   filter: brightness(1);\r\n\t\t }\r\n\t\t 50% {\r\n\t\t   filter: brightness(1.15);\r\n\t\t }\r\n\t\t 100% {\r\n\t\t   filter: brightness(1);\r\n\t\t }\r\n\t}\r\n\t.progress-bar {\r\n\t\t position: relative; \/* \ud83d\udd11 *\/ height: 14px;\r\n\t\t background: #333;\r\n\t\t border-radius: 7px;\r\n\t\t margin: 0.5rem 0;\r\n\t\t overflow: visible; \/* \ud83d\udd11 *\/\r\n\t}\r\n\t.progress-bar:not([data-tooltip = \"\"]):hover::after {\r\n\t\t content: attr(data-tooltip);\r\n\t\t white-space: pre;\r\n\t\t position: absolute;\r\n\t\t bottom: 140% ;\r\n\t\t left: 50% ;\r\n\t\t transform: translateX(-50% );\r\n\t\t background: #222;\r\n\t\t color: #eee;\r\n\t\t padding: 0.4rem 0.6rem;\r\n\t\t border-radius: 6px;\r\n\t\t font-size: 0.8rem;\r\n\t\t z-index: 100;\r\n\t\t pointer-events: none;\r\n\t\t opacity: 0;\r\n\t\t animation: tooltip-in 160ms ease-out forwards;\r\n\t\t cursor: help;\r\n\t} \r\n\r\n\t\/* stale mode, at lost connection with brewing unit *\/\r\n\t#brew-live.stale {\r\n\t\t opacity: 0.6;\r\n\t}\r\n\t#brew-live.stale .heat-icon {\r\n\t\t animation: none;\r\n\t\t filter: grayscale(50% );\r\n\t}\r\n\t#brew-live.stale .pump-indicator {\r\n\t\t opacity: 0.5;\r\n\t}\r\n\t#brew-live.stale .pump-indicator.running .pump-icon {\r\n\t\t animation: none;\r\n\t\t filter: grayscale(50% );\r\n\t\t filter: blur(0.3px);\r\n\t}\r\n\t#brew-live.stale.progress-fill {\r\n\t\t filter: grayscale(100% );\r\n\t\t opacity: 0.5;\r\n\t\t animation: none;\r\n\t}\r\n\t#brew-live.stale.progress-fill.total {\r\n\t\t filter: grayscale(100% );\r\n\t\t opacity: 0.5;\r\n\t} \r\n\t\t\r\n\t#brew-live.stale {\r\n\t  filter: grayscale(40%);\r\n\t}\r\n\r\n\t\/*#brew-live.stale::after {\r\n\t  content: \"\u26a0 Offline\";\r\n\t  position: absolute;\r\n\t  top: 10px;\r\n\t  right: 10px;\r\n\t  font-size: 0.8rem;\r\n\t  opacity: 0.7;\r\n\t}\r\n\t*\/\r\n\t\/* Mobile tool tip fix *\/\r\n\t.progress-bar:not([data-tooltip = \"\"]):hover::after, \r\n\t.progress-bar:not([data-tooltip = \"\"]):active::after, \r\n\t.progress-bar:not([data-tooltip = \"\"]):focus-visible::after {\r\n\t\t content: attr(data-tooltip);\r\n\t}\r\n\t@keyframes tooltip-in {\r\n\t\t from {\r\n\t\t   opacity: 0;\r\n\t\t   transform: translateX(-50% )translateY(6px);\r\n\t\t }\r\n\t\t to {\r\n\t\t   opacity: 1;\r\n\t\t   transform: translateX(-50% )translateY(0);\r\n\t\t }\r\n\t} \r\n\r\n\t\/* Responsive *\/ \r\n@media(max-width: 768px) {\r\n  .brew-main {\r\n    flex-direction: column;\r\n  }\r\n\t\r\n  .left-column,\r\n  .right-column {\r\n    width: 100%;\r\n  }\r\n\r\n  \/* \ud83d\udd25 DET H\u00c4R \u00c4R FIXEN *\/\r\n  .right-column {\r\n    flex: 1 1 auto;        \/* \u2190 skriver \u00f6ver 0 0 220px *\/\r\n    max-width: none;\r\n    min-width: 0;\r\n\r\n    display: flex;\r\n    flex-direction: row;\r\n    gap: 0.5rem;\r\n  }\r\n\r\n  .right-column .brew-card {\r\n    flex: 1 1 0;           \/* \u2190 exakt 50\/50 *\/\r\n    min-width: 0;\r\n\tmin-height: 25vh;   \/* skalar med sk\u00e4rmen *\/\r\n  }\r\n\r\n  .right-column .brew-card:first-child {\r\n    flex: 1 1 0;           \/* \u2190 override desktop-regeln *\/\r\n  }\r\n\t\r\n  .pump-indicator {\r\n\t  height: 100%;\r\n\t  justify-content: center;\r\n\t}\r\n}\r\n\t<\/style>\r\n\r\n\r\n\t<header class=\"brew-header\">\r\n\t\t<div class=\"brew-title\">\r\n\t\t\t<h2 id=\"brew-name\"> Ok\u00e4nd <\/h2>\r\n\t\t\t<div id=\"brew-style\"> ok\u00e4nd <\/div>\r\n\t\t<\/div>\r\n\t\t<div class=\"brew-status\" id=\"brew-status\" title=\"Kontakt med bryggverket\">\r\n\t\t\t<span class=\"status-dot\"><\/span>\r\n\t\t\t<span class=\"status-text\"> Online <\/span>\r\n\t\t<\/div>\r\n\t<\/header>\r\n\r\n\t<div class=\"brew-main\">\r\n\t\t<!--V\u00e4nster: Temperatur-->\r\n\t\t<div class=\"left-column\">\r\n\t\t\t<div class=\"brew-card temp\">\r\n\t\t\t\t<h3> \ud83c\udf21 Temperatur <\/h3>\r\n\t\t\t\t<div id=\"temp-gauge\"><\/div>\r\n\t\t\t<\/div>\r\n\t\t<\/div>\r\n\t\t<!--H\u00f6ger: Heat + Pump-->\r\n\t\t<div class=\"right-column\">\r\n\t\t\t<div class=\"brew-card heat\">\r\n\t\t\t\t<h3>\r\n\t\t\t\t\t<span class=\"heat-icon\"> \ud83d\udd25 <\/span> V\u00e4rme\r\n\t\t\t\t<\/h3>\r\n\t\t\t\t<div class=\"heat-bar\">\r\n\t\t\t\t\t<div class=\"heat-fill\" style=\"height:50%;\"><\/div>\r\n\t\t\t\t<\/div>\r\n\t\t\t\t<div class=\"heat-value\"> 50 % <\/div>\r\n\t\t\t<\/div>\r\n\t\t\t<div class=\"brew-card pump\">\r\n\t\t\t\t<h3> \ud83c\udf00 Pump <\/h3>\r\n\t\t\t\t<div class=\"pump-indicator running\">\r\n\t\t\t\t\t<span class=\"pump-icon\"> \ud83c\udf00 <\/span>\r\n\t\t\t\t\t<span class=\"pump-state\"> Aktiv <\/span>\r\n\t\t\t\t<\/div>\r\n\t\t\t<\/div>\r\n\t\t<\/div>\r\n\t<\/div>\r\n\r\n\t<!--Steg och total progress-->\r\n\t<section class=\"brew-step\">\r\n\t\t<div class=\"brew-step-header\">\r\n\t\t\t<h3>\r\n\t\t\t\t<span class=\"stage-title-text\"><\/span>\r\n\t\t\t\t<span class=\"stage-manual-icon\">\u270b<\/span>\r\n\t\t\t<\/h3>\r\n\r\n\t\t\t<div class=\"stage-timer\">\r\n\t\t\t\t<svg class=\"ring\" width=\"42\" height=\"42\">\r\n\t\t\t\t\t<circle class=\"bg\" cx=\"21\" cy=\"21\" r=\"18\"><\/circle>\r\n\t\t\t\t\t<circle class=\"progress\" cx=\"21\" cy=\"21\" r=\"18\"><\/circle>\r\n\t\t\t\t<\/svg>\r\n\t\t\t\t<div class=\"time\"><\/div>\r\n\t\t\t<\/div>\r\n\t\t<\/div>\r\n\t\t<div class=\"progress-bar step\" tabindex=\"0\" data-tooltip=\"G\u00e5tt: - Kvar: - Slut: -\">\r\n\t\t\t<div class=\"progress-fill\" style=\"width:0%;\"><\/div>\r\n\t\t<\/div>\r\n\t\t<div class=\"progress-meta\">\r\n\t\t\t<span class=\"stageStart\"> 00:00 <\/span>\r\n\t\t\t<span class=\"etaStage\"> 00:00 <\/span>\r\n\t\t<\/div>\r\n\t<\/section>\r\n\r\n\t<section class=\"brew-total\">\r\n\t\t<h3> Total bryggning <\/h3>\r\n\t\t<div class=\"progress-bar total\" tabindex=\"0\" data-tooltip=\"\">\r\n\t\t\t<div class=\"progress-fill\" style=\"width:0%;\"><\/div>\r\n\t\t<\/div>\r\n\t\t<div class=\"progress-meta\">\r\n\t\t\t<span class=\"startTotal\">00:00<\/span>\r\n\t\t\t<span class=\"etaTotal\">00:00:00<\/span>\r\n\t\t<\/div>\r\n\t<\/section>\r\n\t<footer class=\"brew-footer\"> Status: Ej startad <\/footer>\r\n<\/div>\r\n\r\n<!----- JS ----->  \r\n<script src = \"https:\/\/cdn.jsdelivr.net\/npm\/echarts\/dist\/echarts.min.js\" >\r\n\t<\/script><script>document.addEventListener('DOMContentLoaded', function () {\r\n\r\nlet failedPolls = 0;\r\nconst MAX_FAILED_POLLS = 3;\r\n\t\r\n\/\/Holding last timed info\r\nlet lastTiming = null;\r\nlet lastBrew = null;\r\nlet lastProcess = null;\r\nlet lastUpdateTime = null;\r\n\r\nlet stageTimer = {\r\n  active: false,\r\n  startTime: 0,\r\n  startRemaining: 0,\r\n  total: 0\r\n};\r\n\r\nlet tempChart = null;\r\n\t\r\nfunction showLive() {\r\n  const live = document.getElementById('brew-live');\r\n  const fallback = document.getElementById('brew-fallback');\r\n\r\n  if (live) live.style.display = 'block';\r\n  if (fallback) fallback.style.display = 'none';\r\n}\r\n\r\nfunction showFallback() {\r\n  const live = document.getElementById('brew-live');\r\n  const fallback = document.getElementById('brew-fallback');\r\n\r\n  if (live) live.style.display = 'none';\r\n  if (fallback) fallback.style.display = 'block';\r\n}\r\n\r\nfunction initTempGauge() {\r\n  if (tempChart) return;\r\n\r\n  const tempEl = document.getElementById('temp-gauge');\r\n  if (!tempEl) return;\r\n\r\n  tempChart = echarts.init(tempEl);\r\n\r\n  \/\/ Resize observer\r\n  if (!window.gaugeObserver) {\r\n    window.gaugeObserver = new ResizeObserver(() => {\r\n      if (tempChart) tempChart.resize();\r\n    });\r\n    window.gaugeObserver.observe(tempEl);\r\n  }\r\n\r\n  tempChart.setOption({\r\n    tooltip: {\r\n      formatter: () => {\r\n        const option = tempChart.getOption();\r\n        const target = option.series[0].data[0].value;\r\n        const actual = option.series[1].data[0].value;\r\n        const delta = (actual - target).toFixed(1);\r\n        const deltaSign = delta > 0 ? '+' : '';\r\n\t\t\r\n\t\tlet staleNote = '';\r\n\t\tif (document.getElementById('brew-live')?.classList.contains('stale')) {\r\n\t\t  staleNote = '<br><em style=\"opacity:0.7\">Senaste k\u00e4nda v\u00e4rden<\/em>';\r\n\t\t}\r\n\r\n\t\treturn '<strong>Temperatur<\/strong><br>' +\r\n\t\t\t   '\ud83c\udfaf M\u00e5l: ' + target + '\u00b0C<br>' +\r\n\t\t\t   '\ud83c\udf21 Nu: ' + actual + ' \u00b0C<br>' +\r\n\t\t\t   '\u0394T: ' + deltaSign + delta + ' \u00b0C' +\r\n\t\t\t   staleNote;\r\n      }\r\n    },\r\n\r\n    series: [\r\n      {\r\n        type: 'gauge',\r\n        startAngle: 200,\r\n        endAngle: -20,\r\n        radius: '125%',\r\n        center: ['50%', '65%'],\r\n        min: 0,\r\n        max: 100,\r\n        pointer: { show: false },\r\n        progress: { \r\n\t\t\tshow: true, \r\n\t\t\twidth: 4,\r\n\t\t\titemStyle: {\r\n    \t\t\tcolor: '#aaa', \r\n\t\t\t\tborderDash: [4, 4], \r\n\t\t\t\tborderColor: '#ccc', \r\n\t\t\t\tborderWidth: 4\r\n  \t\t\t}\r\n\t\t},\r\n        detail: { show: false }, \/\/ \ud83d\udd11 FIX\r\n\t\taxisLabel: { show: false },\r\n\t\taxisTick: { show: false },\r\n\t\tsplitLine: { show: false },\r\n\t\tdata: [{ value: 75 }]\r\n      },\r\n      {\r\n        type: 'gauge',\r\n        startAngle: 200,\r\n        endAngle: -20,\r\n        radius: '121%',\r\n        center: ['50%', '65%'],\r\n        min: 0,\r\n        max: 100,\r\n        progress: { show: true, \r\n\t\t\t\t\t   width: 14, \r\n\t\t\t\t\t  itemStyle: { \r\n\t\t\t\t\t\tcolor: '#4fc3f7' \/\/ initial f\u00e4rg\r\n\t\t\t\t\t  }\r\n\t  \t\t\t},\r\n        detail: {\r\n          valueAnimation: true, \r\n\t\t  formatter: (value) => { \r\n\t\t\tconst option = tempChart.getOption(); \r\n\t\t\tconst target = option.series[0].data[0].value; \r\n\t  \t\tconst delta = value - target; \r\n\t  \t\tconst isStable = Math.abs(delta) < 0.3; \r\n\t  \r\n\t  \t\treturn isStable ? '\u2243 ' + value.toFixed(1) + ' \u00b0C' : value.toFixed(1) + ' \u00b0C'; \r\n  \t\t\t}\r\n        },\r\n        data: [{ value: 64 }]\r\n      }\r\n    ]\r\n  });\r\n\r\n  setTimeout(() => {\r\n    if (tempChart) tempChart.resize();\r\n  }, 50);\r\n}\r\n\r\n\r\n\/\/ ---------- STATUS ----------\r\nfunction updateConnectionStatus(connected) {\r\n    const el = document.getElementById('brew-status');\r\n    if (!el) return; \/\/ \ud83d\udd11 skydd\r\n\r\n    el.classList.remove('online', 'offline', 'lost');\r\n\r\n    if (connected) {\r\n        el.classList.add('online');\r\n        el.querySelector('.status-text').textContent = 'Online';\r\n    } else if (failedPolls < MAX_FAILED_POLLS) {\r\n        el.classList.add('offline');\r\n        el.querySelector('.status-text').textContent = '\u26a0 Ingen data';\r\n    } else {\r\n        el.classList.add('lost');\r\n        el.querySelector('.status-text').textContent = '\u26a0 Kontakt tappad';\r\n    }\r\n}\r\n\r\n\/\/ ---------- HELPERS ----------\r\n\r\n\/\/info text for tool tip during stale\r\nfunction getStaleNote() {\r\n  if (!document.getElementById('brew-live')?.classList.contains('stale')) return '';\r\n\r\n  const time = formatTimeShort(lastUpdateTime);\r\n\r\n  return '\\n\u26a0 Senaste k\u00e4nda v\u00e4rden (' + time + ')';\r\n}\r\n\t  \r\nfunction formatTimeShort(date) {\r\n  if (!date) return '--:--';\r\n  return String(date.getHours()).padStart(2,'0') + ':' +\r\n         String(date.getMinutes()).padStart(2,'0');\r\n}\r\n\t\t\t\t\t\t  \r\nfunction formatIsoToTime(iso) {\r\n  if (!iso) return '';\r\n  const d = new Date(iso);\r\n  return String(d.getHours()).padStart(2,'0') +':' + String(d.getMinutes()).padStart(2,'0');\r\n}\r\n\r\nfunction formatSeconds(sec) {\r\n  if (!sec) return '0:00';\r\n  sec = Math.floor(sec);\r\n  const h = Math.floor(sec \/ 3600);\r\n  const m = Math.floor((sec % 3600) \/ 60);\r\n  const s = sec % 60;\r\n\t\r\n  if (h > 0) {\r\n\t  return h + ':' + String(m).padStart(2, '0') + ':' + String(s).padStart(2, '0');\r\n  }\r\n\t  return m + ':' + String(s).padStart(2,'0');\r\n}\r\n\r\n\t\/\/ ---------- STAGE HEADER ----------\r\n\tfunction updateStageHeader(state) {\r\n\r\n\t  const titleEl = document.querySelector('.stage-title-text');\r\n\t  const manualIcon = document.querySelector('.stage-manual-icon');\r\n\r\n\t  if (titleEl) {\r\n\t\ttitleEl.textContent = window.currentStageName || '';\r\n\t  }\r\n\r\n\t  if (manualIcon) {\r\n\t\tmanualIcon.classList.toggle('active', !!state.isManual);\r\n\r\n\t\tif (state.isManual) {\r\n\t\t  manualIcon.textContent = state.isCritical ? '\u26a0\ufe0f' : '\u270b';\r\n\t\t}\r\n\t  }\r\n\t}\r\n\r\n\t\/\/ --------- Timer ring in stage header  --------------- \r\n\tfunction updateStageTimerRing(remaining, total, isActive) {\r\n\t  const el = document.querySelector('.stage-timer');\r\n\t  if (!el) return;\r\n\r\n\t  const circle = el.querySelector('.progress');\r\n\t  const text = el.querySelector('.time');\r\n\r\n\t  if (!isActive || !total) {\r\n\t\tel.style.display = 'none';\r\n\t\treturn;\r\n\t  }\r\n\r\n\t  el.style.display = 'block';\r\n\r\n\t  const percent = Math.max(0, Math.min(1, remaining \/ total));\r\n\t  const circumference = 113;\r\n\r\n\t  const offset = circumference * (1 - percent);\r\n\t  circle.style.strokeDashoffset = offset;\r\n\r\n\t  \/\/ text (kort format)\r\n\t  text.textContent = formatSeconds(remaining);\r\n\t\t\r\n\t  if (remaining < 5) {\r\n\t\t  text.textContent = ' \u2713 ';\r\n\t  } else if (remaining < 10) {\r\n  \t\t\ttext.textContent += '\u26a1';\r\n\t  }\r\n\r\n\t  \/\/ f\u00e4rg & state\r\n\t  el.classList.remove('warning', 'critical');\r\n\r\n\t  if (remaining < 60) {\r\n\t\tel.classList.add('warning');\r\n\t  }\r\n\t  if (remaining < 10) {\r\n\t\tel.classList.add('critical');\r\n\t  }\r\n\t}\r\n\r\n\t\/\/Spara timerv\u00e4rde n\u00e4r timer startar\r\n\tfunction syncTimerFromAPI(timing) {\r\n\t  const remaining = timing?.remainingStage ?? 0;\r\n\t  const total = timing?.timerTotal ?? remaining;\r\n\r\n\t  \/\/ INIT (endast f\u00f6rsta g\u00e5ngen)\r\n\t  if (!stageTimer.active) {\r\n\t  \tif (total > 0) {\r\n\t\t\tstageTimer.active = true;\r\n\t\t\tstageTimer.startTime = Date.now();\r\n\t\t\tstageTimer.startRemaining = remaining;\r\n\t\t\tstageTimer.total = total;\r\n\t\t\treturn;\r\n\t\t  }\r\n\t  }\r\n\r\n\t  \/\/ RESYNC (valfri \u2013 men f\u00f6rsiktig!)\r\n\t  const drift = Math.abs(stageTimer.startRemaining - remaining);\r\n\r\n\t  if (drift > 10) { \/\/ \ud83d\udd11 h\u00f6gre tr\u00f6skel!\r\n\t\tstageTimer.startTime = Date.now();\r\n\t\tstageTimer.startRemaining = remaining;\r\n\t  }\r\n\t}\r\n\r\n\t\/\/Support for live timer between api-polls\r\n\tsetInterval(() => {\r\n\t  if (!stageTimer.active) return;\r\n\r\n\t  const elapsed = (Date.now() - stageTimer.startTime) \/ 1000;\r\n  \t  const remaining = Math.max(0, stageTimer.startRemaining - elapsed);\r\n\r\n  \t  updateStageTimerRing(remaining, stageTimer.total, true);\r\n\r\n\t}, 1000);\r\n\t\r\n\t\/\/check for stale\r\n\tfunction getConnectionState(data) {\r\n\t  const isConnected = data?.brew?.connected === true;\r\n\t  const isPollingAlive = failedPolls < MAX_FAILED_POLLS;\r\n\r\n\t  const isStale = !isConnected || !isPollingAlive;\r\n\r\n\t  return {\r\n\t\tisConnected,\r\n\t\tisPollingAlive,\r\n\t\tisStale\r\n\t  };\r\n\t}\r\n\r\n\/\/ ---------- MAIN RENDER ----------\r\nfunction renderLivePanel(data) {\r\n  if (!data) return;\r\n\t  \r\n\t\/\/Get UI state\r\n\tconst state = getConnectionState(data);\r\n\t  \r\n\t\/\/Save last online time stamp\r\n\tif (!state.isStale) {\r\n\t  lastUpdateTime = new Date();\r\n\t}\r\n\t  \r\n\t\/\/Update last timing info\r\n\tif (!state.isStale) {\r\n\t\tif (data.timing) lastTiming = data.timing;\r\n\t  \tif (data.brew) lastBrew = data.brew;\r\n\t  \tif (data.process) lastProcess = data.process;\r\n\t}\r\n\t\r\n\tconst timing = state.isStale ? lastTiming : data.timing;\r\n\tconst brew = state.isStale ? lastBrew : data.brew; \r\n\t\r\n\r\n\t\/\/ s\u00e4tt UI-state\r\n\tconst liveEl = document.getElementById('brew-live');\r\n\tif (liveEl) {\r\n\t  liveEl.classList.toggle('stale', state.isStale);\r\n\t}\r\n\r\n  \/\/ --- Stage name ---\r\n  const process = state.isStale ? lastProcess : data.process;\r\n  window.currentStageName = process?.stage || '';\r\n\r\n\t\/\/ --- Stage header (CLEAN) ---\r\n\tconst manualIcon = document.querySelector('.stage-manual-icon');\r\n\r\n\tconst stageName = window.currentStageName || '';\r\n\r\n\tupdateStageHeader({\r\n\t  isManual: brew?.isManual,\r\n\t  isCritical: brew?.isCritical\r\n\t});\r\n  \r\n\tconst h3 = document.querySelector('.brew-step h3');\r\n\th3.classList.toggle('manual-active', brew?.isManual);  \r\n\r\n\t\/\/ icon only\r\n\tif (manualIcon) {\r\n\t  manualIcon.classList.toggle('active', !!brew?.isManual);\r\n\r\n\t  if (brew?.isManual) {\r\n\t\tmanualIcon.textContent = brew?.isCritical ? '\u26a0\ufe0f' : '\u270b';\r\n\t  }\r\n}\r\n\t  \r\n\t\/\/ Timer ring\r\n\tconst isTimerActive = brew?.timerActive === true;\r\n\tconst isManual = brew?.isManual === true;\r\n\r\n\tif (isManual) {\r\n\t  stageTimer.active = false;\r\n\t  updateStageTimerRing(0, 0, false);\r\n\t} else if (isTimerActive) {\r\n\t\tif (timing) {\r\n\t  \t\tsyncTimerFromAPI(timing);\r\n\t\t}\r\n\t}\r\n\t\r\n\t\/\/reset stageTimer when timer is inactive\r\n\tif (!isTimerActive || isManual) {\r\n\t  stageTimer = {\r\n\t\tactive: false,\r\n\t\tstartTime: 0,\r\n\t\tstartRemaining: 0,\r\n\t\ttotal: 0\r\n\t  };\r\n\r\n\t  updateStageTimerRing(0, 0, false);\r\n\t}\r\n  \r\n  \/\/console.log (isStale); \r\n  \/\/Stale settings\r\n  \r\n\tconst timerEl = document.querySelector('.brew-stage-timer');\r\n\tif (timerEl) {\r\n\t  timerEl.classList.toggle('stale', state.isStale);\r\n\t}\r\n\t  \r\n  document \r\n\t  .getElementById('brew-live') \r\n\t  .classList.toggle('stale', state.isStale); \r\n  \r\n\t\/\/ Temperatur\r\n\tconst target = data.temperature?.target ?? 0;\r\n\tconst actual = data.temperature?.current ?? 0;\r\n\tconst delta = actual - target;\r\n\r\n\tconst isStable = Math.abs(delta) < 0.3;\r\n\r\n\t\/\/ --- Basf\u00e4rger ---\r\n\tlet pointerColor = '#4fc3f7';\r\n\tlet tempColor = '#4fc3f7';\r\n\r\n\t\/\/ --- Temperatur-logik ---\r\n\tif (isStable) {\r\n\t  pointerColor = '#4caf50';\r\n\t  tempColor = '#4caf50';\r\n\t} else if (delta > 0) {\r\n\t  pointerColor = '#ff7043';\r\n\t}\r\n\r\n\tif (delta > 2) tempColor = '#ff9800';\r\n\tif (delta > 5) tempColor = '#f44336';\r\n\r\n\t\/\/ --- \ud83d\udd11 STATE OVERRIDE ---\r\n\tif (state.isStale) {\r\n\t  pointerColor = '#777';\r\n\t  tempColor = '#777';\r\n\t}\r\n  \r\n\t\/\/ breathe animation\r\n\tconst gaugeEl = document.getElementById('temp-gauge');\r\n\tif (gaugeEl) {\r\n\t  gaugeEl.classList.toggle('stable', isStable && !state.isStale);\r\n\t}\r\n\r\n  \/\/ --- Titles ---\r\n  const nameEl = document.getElementById('brew-name');\r\n  if (nameEl) nameEl.textContent = brew?.name || 'Ok\u00e4nd';\r\n\r\n  const styleEl = document.getElementById('brew-style');\r\n  if (styleEl) styleEl.textContent = brew?.style || '';\r\n\r\n\r\n\t\/\/ gauge update\r\n\tif (tempChart) {\r\n\t  tempChart.setOption({\r\n\t\tseries: [\r\n\t\t  { data: [{ value: target }] },\r\n\t\t  {\r\n\t\t\tdata: [{ value: actual }],\r\n\t\t\tprogress: {\r\n\t\t\t  itemStyle: { color: tempColor }\r\n\t\t\t},\r\n\t\t\tpointer: {\r\n\t\t\t  width: 8,\r\n\t\t\t  length: '65%',\r\n\t\t\t  itemStyle: { color: pointerColor }\r\n\t\t\t}\r\n\t\t  }\r\n\t\t]\r\n\t  });\r\n\t}\r\n  \r\n\r\n  \/\/ --- Heat ---\r\n  const heatFill = document.querySelector('.heat-fill');\r\n  const heatValue = document.querySelector('.heat-value');\r\n  const heatCard = document.querySelector('.brew-card.heat');\r\n\r\n  const heat = data.heater?.level ?? 0;\r\n\r\n  if (heatFill) heatFill.style.height = heat + '%';\r\n  if (heatValue) heatValue.textContent = heat + ' %';\r\n\r\n  if (heatCard) {\r\n    heatCard.classList.toggle('heat-on', !!data.heater?.on);\r\n    heatCard.classList.toggle('heat-low', heat > 0 && heat < 30);\r\n    heatCard.classList.toggle('heat-high', heat >= 70);\r\n  }\r\n\r\n  \/\/ --- Pump ---\r\n  const pumpEl = document.querySelector('.pump-indicator');\r\n  if (pumpEl) {\r\n    pumpEl.classList.toggle('running', !!data.pump?.on);\r\n    pumpEl.classList.toggle('stopped', !data.pump?.on);\r\n\r\n    const stateEl = pumpEl.querySelector('.pump-state');\r\n    if (stateEl) {\r\n      stateEl.textContent = data.pump?.on ? 'Aktiv' : 'Av';\r\n    }\r\n  }\r\n  \r\n  \/\/Stage caption live timer\r\n\r\n  \/\/ --- Stage progress ---\r\n  const stageFill = document.querySelector('.progress-bar.step .progress-fill');\r\n  if (stageFill) {\r\n    stageFill.style.width = (brew?.progressStage ?? 0) + '%';\r\n\r\n    stageFill.classList.remove('running', 'manual', 'timed');\r\n\r\n    if (brew?.isManual) {\r\n      stageFill.classList.add('manual');\r\n    } else if (brew?.timerActive) {\r\n      stageFill.classList.add('timed');\r\n    } else {\r\n      stageFill.classList.add('running');\r\n    }\r\n  }\r\n\r\n  \/\/ Tooltip stage\r\n  const stepBar = document.querySelector('.progress-bar.step');\r\n  if (stepBar && timing) {\r\n    let tooltip = '';\r\n\r\n\tif (brew.isManual) { \r\n\t\ttooltip = 'V\u00e4ntar p\u00e5 bekr\u00e4ftelse\\n' + \r\n\t\t'P\u00e5g\u00e5tt tid: ' + formatSeconds(timing.totalStage-timing.remainingStage) + '\\n' + \r\n\t\t'Ber\u00e4knad sluttid:' + formatIsoToTime(timing.etaStage); \r\n\t} else if (brew.timerActive) { \r\n\t\ttooltip = 'P\u00e5g\u00e5tt tid: ' + formatSeconds(timing.totalStage-timing.remainingStage) + '\\n' + \r\n\t\t'Kvarvarande timer-tid: ' + formatSeconds(timing.remainingStage) + '\\n' + \r\n\t\t'Ber\u00e4knad sluttid: ' + formatIsoToTime(timing.etaStage); \r\n\t} else { \r\n\t\ttooltip = 'P\u00e5g\u00e5tt tid: ' + formatSeconds(timing.totalStage-timing.remainingStage) + '\\n' + \r\n\t\t'Ber\u00e4knad kvarvarande tid: ' + formatSeconds(timing.remainingStage) + '\\n' + \r\n\t\t'Ber\u00e4knad sluttid: ' + formatIsoToTime(timing.etaStage); \r\n\t}\r\n\r\n    stepBar.dataset.tooltip = tooltip + getStaleNote();\r\n }\r\n  \/\/ Time etaStage\r\n  const totalStage = timing.totalStage ?? (timing.remainingStage + (timing.elapsedStage ?? 0));\r\n  document.querySelector('.etaStage').textContent = formatSeconds(totalStage ?? 0);\r\n\t\t\t\t\t\t\t \r\n  \/\/ --- Total progress ---\r\n  const totalFill = document.querySelector('.progress-bar.total .progress-fill');\r\n  if (totalFill) {\r\n    totalFill.style.width = (brew?.progress ?? 0) + '%';\r\n\r\n    totalFill.classList.remove('manual', 'timed', 'total');\r\n    totalFill.classList.add('total');\r\n\r\n    if (brew?.isManual) {\r\n      totalFill.classList.add('manual');\r\n    } else if (brew?.timerActive && brew?.isLastStage) {\r\n      totalFill.classList.add('timed');\r\n    }\r\n  }\r\n\r\n  \/\/ Tooltip total\r\n  const totalBar = document.querySelector('.progress-bar.total');\r\n  if (totalBar && timing) {\r\n    let tooltip = '';\r\n\r\n    if (brew?.isManual) {\r\n      tooltip =\r\n        'V\u00e4ntar p\u00e5 bekr\u00e4ftelse\\n' +\r\n        'P\u00e5g\u00e5tt tid: ' + formatSeconds(timing.elapsed ?? 0) + '\\n' +\r\n        'Ber\u00e4knad sluttid: ' + formatIsoToTime(timing.eta);\r\n    } else if (brew?.timerActive && brew?.isLastStage) {\r\n      tooltip =\r\n        'P\u00e5g\u00e5t tid:: ' + formatSeconds(timing.elapsed ?? 0) + '\\n' +\r\n        'Kvarvarande timer-tid: ' + formatSeconds(timing.remaining ?? 0) + '\\n' +\r\n        'Ber\u00e4knad sluttid: ' + formatIsoToTime(timing.eta);\r\n    } else {\r\n      tooltip =\r\n        'P\u00e5g\u00e5tt tid: ' + formatSeconds(timing.elapsed ?? 0) + '\\n' +\r\n        'Ber\u00e4knad kvarvarande tid: ' + formatSeconds(timing.remaining ?? 0) + '\\n' +\r\n        'Ber\u00e4knad sluttid: ' + formatIsoToTime(timing.eta);\r\n    }\r\n\r\n    totalBar.dataset.tooltip = tooltip + getStaleNote();\r\n  }\r\n\r\n  \/\/ --- Times ---\r\n  const startEl = document.querySelector('.startTotal');\r\n  if (startEl) {\r\n    startEl.textContent = formatIsoToTime(timing?.startedAt);\r\n  }\r\n\r\n  const etaEl = document.querySelector('.etaTotal');\r\n  if (etaEl) {\r\n    etaEl.textContent = formatIsoToTime(timing?.eta);\r\n  }\r\n\r\n  \/\/ --- Footer ---\r\n  const footer = document.querySelector('.brew-footer');\r\n  if (footer) {\r\n    footer.textContent = 'Status: ' + (data.process?.step || '');\r\n  }\r\n}\r\n\r\n\/\/ ---------- POLL ----------\r\nconst API_URL = '\/api\/getbrewdata.php';\r\n\r\nasync function pollBrewData() {\r\n  try {\r\n    const res = await fetch(API_URL, { cache: 'no-store' });\r\n    const data = await res.json();\r\n\r\n    if (data.status !== 'ok') throw new Error();\r\n\r\n\tfailedPolls = 0;\r\n\t  \r\n\tif (data.brew && data.brew.active === false) {\r\n  \t\tshowFallback();\r\n  \t\treturn;\r\n\t}\r\n\tshowLive();\r\n\r\n    initTempGauge();\r\n    const state = getConnectionState(data);\r\n\tupdateConnectionStatus(state.isConnected && state.isPollingAlive);\r\n    renderLivePanel(data);\r\n\r\n  } catch (e) {\r\n    failedPolls++;\r\n\tupdateConnectionStatus(false);\r\n\r\n\tif (failedPolls >= MAX_FAILED_POLLS) {\r\n  \t\tshowFallback();\r\n\t}\r\n  }\r\n}\r\n\r\npollBrewData();\r\nsetInterval(pollBrewData, 5000);\r\n\r\n});<\/script><\/p>\n\n\n\n<h2 class=\"wp-block-heading has-large-font-size\">P\u00e5g\u00e5ende j\u00e4sningar<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">H\u00e4r listas de bryggningar som just nu st\u00e5r och puttrar i j\u00e4sk\u00e4rl.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><figure class=\"wp-block-table is-style-regular MildasFermentingBeerTable\"><table><tbody><tr><td><strong>Batchnummer<\/strong><\/td><td><strong>Bryggning<\/strong><\/td><td><strong>Bryggdatum<\/strong><\/td><td><strong>J\u00e4sstart<\/strong><\/td><\/tr><tr><td><\/td><td>Mildas Skattkista<\/td><td>2026-08-08<\/td><td>2026-08-08<\/td><\/tr><tr><td colspan=\"4\"><div class=\"wp-block-gutentor-e6 gutentor-element gutentor-element-image text-align-center-desktop\"><div class=\"gutentor-element-image-box\"><div class=\"gutentor-image-thumb\"><img decoding=\"async\" class=\"normal-image\"<img decoding=\"async\" class=\"MildasKeepAspectRatio\" src=\"\/brewfather\/images\/DEOt5JOBenUeskriNRWWQ3YyG14sYS.png?1786257916\"><\/div><\/div><\/div><\/td><\/tr><tr><td><\/td><td>Bryggm\u00e4starnas hemlighet<\/td><td>2026-08-08<\/td><td>2026-08-08<\/td><\/tr><tr><td colspan=\"4\"><div class=\"wp-block-gutentor-e6 gutentor-element gutentor-element-image text-align-center-desktop\"><div class=\"gutentor-element-image-box\"><div class=\"gutentor-image-thumb\"><img decoding=\"async\" class=\"normal-image\"<img decoding=\"async\" class=\"MildasKeepAspectRatio\" src=\"\/brewfather\/images\/bcq9I2n4t0JMbLHPejDsL2ByeEHtGp.png?1786257916\"><\/div><\/div><\/div><\/td><\/tr><\/tbody><\/table><\/figure><\/p>\n\n\n\n<h2 class=\"wp-block-heading has-large-font-size\">P\u00e5g\u00e5ende kolsyrning<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">H\u00e4r listas de bryggningar som \u00e4r lagda p\u00e5 flaska och \u00e4nnu mognar.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><figure class=\"wp-block-table is-style-regular MildasCondBeerTable\"><table><tbody><tr><td><strong>Batchnummer<\/strong><\/td><td><strong>Bryggning<\/strong><\/td><td><strong>Bryggdatum<\/strong><\/td><td><strong>J\u00e4sstart<\/strong><\/td><td><strong>Flaskdatum<\/strong><\/td><\/tr><tr><td>403-039-015-01-1<\/td><td>Rabalder med enbart rabarber<\/td><td>2026-07-11<\/td><td>2026-07-11<\/td><td>2026-08-08<\/td><\/tr><tr><td>403-039-015-01-2<\/td><td>Rabalder<\/td><td>2026-07-11<\/td><td>2026-07-11<\/td><td>2026-08-08<\/td><\/tr><\/tbody><\/table><\/figure><\/p>\n\n\n\n<div style=\"height:67px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Versionshistorik API<\/p>\n\n\n\n<figure class=\"wp-block-table is-style-regular has-small-font-size\"><table style=\"border-style:none;border-width:0px\"><tbody><tr><td><a href=\"\/index.php\/uncategorized\/api-for-mildasbryggarna\/\">Version 1.0<\/a><br>Version 2.0<\/td><td>Ursprunglig release. St\u00f6d f\u00f6r bryggstatus samt j\u00e4skurvor<br>Lagt till panel som visar live n\u00e4r vi brygger \u00f6l<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>H\u00e4r \u00e4r v\u00e5r tavla \u00f6ver planerade och p\u00e5g\u00e5ende projekt. Vi sk\u00f6ter all dokumentation och planering i Brewfather och via Brewfathers API har vi m\u00f6jlighet att direkt uppdatera v\u00e5r webbplats med aktuell info. Vi anv\u00e4nder \u00e4ven ett egenytvecklat centralt bryggsystem som \u00f6vervakar och styr v\u00e5ra bryggenheter. Ett API var en av de saker som v\u00e4gde tungt [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":5303,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_eb_attr":"","footnotes":""},"class_list":["post-6898","page","type-page","status-publish","hentry"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.2 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>P\u00e5 g\u00e5ng - Mildasberg<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/mildasberg.se\/index.php\/mildasbryggarna\/pa-gang\/\" \/>\n<meta property=\"og:locale\" content=\"sv_SE\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"P\u00e5 g\u00e5ng - Mildasberg\" \/>\n<meta property=\"og:description\" content=\"H\u00e4r \u00e4r v\u00e5r tavla \u00f6ver planerade och p\u00e5g\u00e5ende projekt. Vi sk\u00f6ter all dokumentation och planering i Brewfather och via Brewfathers API har vi m\u00f6jlighet att direkt uppdatera v\u00e5r webbplats med aktuell info. Vi anv\u00e4nder \u00e4ven ett egenytvecklat centralt bryggsystem som \u00f6vervakar och styr v\u00e5ra bryggenheter. Ett API var en av de saker som v\u00e4gde tungt [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/mildasberg.se\/index.php\/mildasbryggarna\/pa-gang\/\" \/>\n<meta property=\"og:site_name\" content=\"Mildasberg\" \/>\n<meta property=\"article:modified_time\" content=\"2026-05-07T08:36:35+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/mildasberg.se\/wp-content\/uploads\/2025\/12\/Grainfather-scaled-e1766475134584.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1024\" \/>\n\t<meta property=\"og:image:height\" content=\"770\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Ber\u00e4knad l\u00e4stid\" \/>\n\t<meta name=\"twitter:data1\" content=\"2 minuter\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/mildasberg.se\\\/index.php\\\/mildasbryggarna\\\/pa-gang\\\/\",\"url\":\"https:\\\/\\\/mildasberg.se\\\/index.php\\\/mildasbryggarna\\\/pa-gang\\\/\",\"name\":\"P\u00e5 g\u00e5ng - Mildasberg\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/mildasberg.se\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/mildasberg.se\\\/index.php\\\/mildasbryggarna\\\/pa-gang\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/mildasberg.se\\\/index.php\\\/mildasbryggarna\\\/pa-gang\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/mildasberg.se\\\/wp-content\\\/uploads\\\/2025\\\/12\\\/Grainfather-768x1024.jpg\",\"datePublished\":\"2025-12-23T07:33:20+00:00\",\"dateModified\":\"2026-05-07T08:36:35+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/mildasberg.se\\\/index.php\\\/mildasbryggarna\\\/pa-gang\\\/#breadcrumb\"},\"inLanguage\":\"sv-SE\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/mildasberg.se\\\/index.php\\\/mildasbryggarna\\\/pa-gang\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"sv-SE\",\"@id\":\"https:\\\/\\\/mildasberg.se\\\/index.php\\\/mildasbryggarna\\\/pa-gang\\\/#primaryimage\",\"url\":\"https:\\\/\\\/mildasberg.se\\\/wp-content\\\/uploads\\\/2025\\\/12\\\/Grainfather-scaled-e1766475134584.jpg\",\"contentUrl\":\"https:\\\/\\\/mildasberg.se\\\/wp-content\\\/uploads\\\/2025\\\/12\\\/Grainfather-scaled-e1766475134584.jpg\",\"width\":1024,\"height\":770,\"caption\":\"Planering\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/mildasberg.se\\\/index.php\\\/mildasbryggarna\\\/pa-gang\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Hem\",\"item\":\"https:\\\/\\\/mildasberg.se\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"MildasBryggarna\",\"item\":\"https:\\\/\\\/mildasberg.se\\\/index.php\\\/mildasbryggarna\\\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"P\u00e5 g\u00e5ng\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/mildasberg.se\\\/#website\",\"url\":\"https:\\\/\\\/mildasberg.se\\\/\",\"name\":\"Mildasberg\",\"description\":\"H\u00e4r lever vi efter principen &quot;Hur sv\u00e5rt kan det vara?&quot;\",\"publisher\":{\"@id\":\"https:\\\/\\\/mildasberg.se\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/mildasberg.se\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"sv-SE\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/mildasberg.se\\\/#organization\",\"name\":\"Mildasberg\",\"url\":\"https:\\\/\\\/mildasberg.se\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"sv-SE\",\"@id\":\"https:\\\/\\\/mildasberg.se\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/mildasberg.se\\\/wp-content\\\/uploads\\\/2023\\\/03\\\/mildasberg.jpg\",\"contentUrl\":\"https:\\\/\\\/mildasberg.se\\\/wp-content\\\/uploads\\\/2023\\\/03\\\/mildasberg.jpg\",\"width\":706,\"height\":529,\"caption\":\"Mildasberg\"},\"image\":{\"@id\":\"https:\\\/\\\/mildasberg.se\\\/#\\\/schema\\\/logo\\\/image\\\/\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"P\u00e5 g\u00e5ng - Mildasberg","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/mildasberg.se\/index.php\/mildasbryggarna\/pa-gang\/","og_locale":"sv_SE","og_type":"article","og_title":"P\u00e5 g\u00e5ng - Mildasberg","og_description":"H\u00e4r \u00e4r v\u00e5r tavla \u00f6ver planerade och p\u00e5g\u00e5ende projekt. Vi sk\u00f6ter all dokumentation och planering i Brewfather och via Brewfathers API har vi m\u00f6jlighet att direkt uppdatera v\u00e5r webbplats med aktuell info. Vi anv\u00e4nder \u00e4ven ett egenytvecklat centralt bryggsystem som \u00f6vervakar och styr v\u00e5ra bryggenheter. Ett API var en av de saker som v\u00e4gde tungt [&hellip;]","og_url":"https:\/\/mildasberg.se\/index.php\/mildasbryggarna\/pa-gang\/","og_site_name":"Mildasberg","article_modified_time":"2026-05-07T08:36:35+00:00","og_image":[{"width":1024,"height":770,"url":"https:\/\/mildasberg.se\/wp-content\/uploads\/2025\/12\/Grainfather-scaled-e1766475134584.jpg","type":"image\/jpeg"}],"twitter_card":"summary_large_image","twitter_misc":{"Ber\u00e4knad l\u00e4stid":"2 minuter"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/mildasberg.se\/index.php\/mildasbryggarna\/pa-gang\/","url":"https:\/\/mildasberg.se\/index.php\/mildasbryggarna\/pa-gang\/","name":"P\u00e5 g\u00e5ng - Mildasberg","isPartOf":{"@id":"https:\/\/mildasberg.se\/#website"},"primaryImageOfPage":{"@id":"https:\/\/mildasberg.se\/index.php\/mildasbryggarna\/pa-gang\/#primaryimage"},"image":{"@id":"https:\/\/mildasberg.se\/index.php\/mildasbryggarna\/pa-gang\/#primaryimage"},"thumbnailUrl":"https:\/\/mildasberg.se\/wp-content\/uploads\/2025\/12\/Grainfather-768x1024.jpg","datePublished":"2025-12-23T07:33:20+00:00","dateModified":"2026-05-07T08:36:35+00:00","breadcrumb":{"@id":"https:\/\/mildasberg.se\/index.php\/mildasbryggarna\/pa-gang\/#breadcrumb"},"inLanguage":"sv-SE","potentialAction":[{"@type":"ReadAction","target":["https:\/\/mildasberg.se\/index.php\/mildasbryggarna\/pa-gang\/"]}]},{"@type":"ImageObject","inLanguage":"sv-SE","@id":"https:\/\/mildasberg.se\/index.php\/mildasbryggarna\/pa-gang\/#primaryimage","url":"https:\/\/mildasberg.se\/wp-content\/uploads\/2025\/12\/Grainfather-scaled-e1766475134584.jpg","contentUrl":"https:\/\/mildasberg.se\/wp-content\/uploads\/2025\/12\/Grainfather-scaled-e1766475134584.jpg","width":1024,"height":770,"caption":"Planering"},{"@type":"BreadcrumbList","@id":"https:\/\/mildasberg.se\/index.php\/mildasbryggarna\/pa-gang\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Hem","item":"https:\/\/mildasberg.se\/"},{"@type":"ListItem","position":2,"name":"MildasBryggarna","item":"https:\/\/mildasberg.se\/index.php\/mildasbryggarna\/"},{"@type":"ListItem","position":3,"name":"P\u00e5 g\u00e5ng"}]},{"@type":"WebSite","@id":"https:\/\/mildasberg.se\/#website","url":"https:\/\/mildasberg.se\/","name":"Mildasberg","description":"H\u00e4r lever vi efter principen &quot;Hur sv\u00e5rt kan det vara?&quot;","publisher":{"@id":"https:\/\/mildasberg.se\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/mildasberg.se\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"sv-SE"},{"@type":"Organization","@id":"https:\/\/mildasberg.se\/#organization","name":"Mildasberg","url":"https:\/\/mildasberg.se\/","logo":{"@type":"ImageObject","inLanguage":"sv-SE","@id":"https:\/\/mildasberg.se\/#\/schema\/logo\/image\/","url":"https:\/\/mildasberg.se\/wp-content\/uploads\/2023\/03\/mildasberg.jpg","contentUrl":"https:\/\/mildasberg.se\/wp-content\/uploads\/2023\/03\/mildasberg.jpg","width":706,"height":529,"caption":"Mildasberg"},"image":{"@id":"https:\/\/mildasberg.se\/#\/schema\/logo\/image\/"}}]}},"_links":{"self":[{"href":"https:\/\/mildasberg.se\/index.php\/wp-json\/wp\/v2\/pages\/6898","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mildasberg.se\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/mildasberg.se\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/mildasberg.se\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mildasberg.se\/index.php\/wp-json\/wp\/v2\/comments?post=6898"}],"version-history":[{"count":18,"href":"https:\/\/mildasberg.se\/index.php\/wp-json\/wp\/v2\/pages\/6898\/revisions"}],"predecessor-version":[{"id":7070,"href":"https:\/\/mildasberg.se\/index.php\/wp-json\/wp\/v2\/pages\/6898\/revisions\/7070"}],"up":[{"embeddable":true,"href":"https:\/\/mildasberg.se\/index.php\/wp-json\/wp\/v2\/pages\/5303"}],"wp:attachment":[{"href":"https:\/\/mildasberg.se\/index.php\/wp-json\/wp\/v2\/media?parent=6898"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}