{"id":11649,"date":"2026-06-19T09:59:21","date_gmt":"2026-06-19T07:59:21","guid":{"rendered":"https:\/\/www.gmz-it.de\/?page_id=11649"},"modified":"2026-09-02T10:46:14","modified_gmt":"2026-09-02T08:46:14","slug":"cloud-business-apps","status":"publish","type":"page","link":"https:\/\/www.gmz-it.de\/index.php\/cloud-business-apps\/","title":{"rendered":"Cloud Business Apps"},"content":{"rendered":"<div data-colibri-id=\"11649-c1\" class=\"style-11225 style-local-11649-c1 position-relative\">\n  <!---->\n  <div data-colibri-component=\"section\" data-colibri-id=\"11649-c2\" id=\"about\" class=\"h-section h-section-global-spacing d-flex align-items-lg-center align-items-md-center align-items-center style-11226 style-local-11649-c2 position-relative\">\n    <!---->\n    <!---->\n    <div class=\"h-section-grid-container h-section-boxed-container\">\n      <!---->\n      <div data-colibri-id=\"11649-c3\" class=\"h-row-container gutters-row-lg-0 gutters-row-md-0 gutters-row-0 gutters-row-v-lg-0 gutters-row-v-md-0 gutters-row-v-0 style-11227 style-local-11649-c3 position-relative\">\n        <!---->\n        <div class=\"h-row justify-content-lg-center justify-content-md-center justify-content-center align-items-lg-stretch align-items-md-stretch align-items-stretch gutters-col-lg-0 gutters-col-md-0 gutters-col-0 gutters-col-v-lg-0 gutters-col-v-md-0 gutters-col-v-0\">\n          <!---->\n          <div class=\"h-column h-column-container d-flex h-col-lg-auto h-col-md-auto h-col-auto style-11228-outer style-local-11649-c4-outer\">\n            <div data-colibri-id=\"11649-c4\" class=\"d-flex h-flex-basis h-column__inner h-px-lg-0 h-px-md-0 h-px-0 v-inner-lg-0 v-inner-md-0 v-inner-0 style-11228 style-local-11649-c4 position-relative\">\n              <!---->\n              <!---->\n              <div class=\"w-100 h-y-container h-column__content h-column__v-align flex-basis-100 align-self-lg-start align-self-md-start align-self-start\">\n                <!---->\n                <div data-colibri-id=\"11649-c5\" class=\"h-global-transition-all h-heading style-11230 style-local-11649-c5 position-relative h-element\">\n                  <!---->\n                  <div class=\"h-heading__outer style-11230 style-local-11649-c5\">\n                    <!---->\n                    <!---->\n                    <h2 class=\"\"><span style=\"font-size: 85%; color: rgb(255, 255, 255);\"><br><br>Cloud Business Apps<\/span><\/h2>\n                  <\/div>\n                <\/div>\n                <div data-colibri-id=\"11649-c6\" class=\"h-text h-text-component style-11231 style-local-11649-c6 position-relative h-element\">\n                  <!---->\n                  <!---->\n                  <div class=\"\">\n                    <p><span style=\"color: rgb(255, 255, 255);\">Mit unseren Cloud Business Apps bieten wir eine vollst\u00e4ndig verwaltete Business-Cloud auf Open-Source-Basis, die exklusiv f\u00fcr Ihr Unternehmen in einer Private Cloud l\u00e4uft. Das bedeutet: modernste Software, keine Lizenzkosten, Daten bleiben in Deutschland und wir k\u00fcmmern uns um alles im Hintergrund. Was Sie bekommen: Dateiablage, Passwort Management, digitale Dokumentenmanagement, Workflow Automatisierung, PDF-Bearbeitung und mehr. Jedes Modul ist einzeln buchbar und sofort einsatzbereit. Alles \u00fcber den Browser nutzbar, alles \u00fcber ein zentrales Dashboard gesteuert.<\/span><\/p>\n                  <\/div>\n                <\/div>\n              <\/div>\n            <\/div>\n          <\/div>\n          <div class=\"h-column h-column-container d-flex h-col-lg-auto h-col-md-auto h-col-auto style-11232-outer style-local-11649-c7-outer\">\n            <div data-colibri-id=\"11649-c7\" class=\"d-flex h-flex-basis h-column__inner h-px-lg-0 h-px-md-0 h-px-0 v-inner-lg-0 v-inner-md-0 v-inner-0 style-11232 style-local-11649-c7 position-relative\">\n              <!---->\n              <!---->\n              <div class=\"w-100 h-y-container h-column__content h-column__v-align flex-basis-100 align-self-lg-center align-self-md-center align-self-start\">\n                <!---->\n                <div data-colibri-id=\"11649-c8\" class=\"d-block style-11235 style-local-11649-c8 position-relative h-element\">\n                  <!---->\n                  <div class=\"h-image__frame-container-outer\">\n                    <div class=\"h-image__frame-container\">\n                      <!---->\n                      <!---->\n                      <img loading=\"lazy\" decoding=\"async\" width=\"1078\" height=\"1349\" src=\"https:\/\/www.gmz-it.de\/wp-content\/uploads\/2026\/06\/cropped-cropped-Dashboard-Cloud-Business-Apps.png\" class=\"wp-image-12086 style-11235-image style-local-11649-c8-image\" alt=\"\" srcset=\"https:\/\/www.gmz-it.de\/wp-content\/uploads\/2026\/06\/cropped-cropped-Dashboard-Cloud-Business-Apps.png 1078w, https:\/\/www.gmz-it.de\/wp-content\/uploads\/2026\/06\/cropped-cropped-Dashboard-Cloud-Business-Apps-240x300.png 240w, https:\/\/www.gmz-it.de\/wp-content\/uploads\/2026\/06\/cropped-cropped-Dashboard-Cloud-Business-Apps-818x1024.png 818w, https:\/\/www.gmz-it.de\/wp-content\/uploads\/2026\/06\/cropped-cropped-Dashboard-Cloud-Business-Apps-768x961.png 768w\" sizes=\"auto, (max-width: 1078px) 100vw, 1078px\" \/>\n                      <div class=\"h-image__frame h-hide-lg h-hide-md h-hide-sm style-11235-frameImage style-local-11649-c8-frameImage\"><\/div>\n                    <\/div>\n                  <\/div>\n                <\/div>\n                <div data-colibri-id=\"11649-c9\" class=\"h-x-container style-11387 style-local-11649-c9 position-relative h-element\">\n                  <!---->\n                  <div class=\"h-x-container-inner style-dynamic-11649-c9-group style-11387-spacing style-local-11649-c9-spacing\"><span class=\"h-button__outer style-11388-outer style-local-11649-c10-outer d-inline-flex h-element\"><a h-use-smooth-scroll=\"true\" href=\"https:\/\/www.gmz-it.de\/index.php\/kontakt\/ \"  data-colibri-id=\"11649-c10\" class=\"d-flex w-100 align-items-center h-button justify-content-lg-center justify-content-md-center justify-content-center style-11388 style-local-11649-c10 position-relative\"><!----><!----> <span>Fragen? Wir beraten Sie gern!<\/span><\/a>\n                    <\/span>\n                  <\/div>\n                <\/div>\n              <\/div>\n            <\/div>\n          <\/div>\n        <\/div>\n      <\/div>\n      <div data-colibri-id=\"11649-c11\" class=\"h-row-container gutters-row-lg-2 gutters-row-md-2 gutters-row-0 gutters-row-v-lg-2 gutters-row-v-md-2 gutters-row-v-2 style-11295 style-local-11649-c11 position-relative\">\n        <!---->\n        <div class=\"h-row justify-content-lg-center justify-content-md-center justify-content-center align-items-lg-stretch align-items-md-stretch align-items-stretch gutters-col-lg-2 gutters-col-md-2 gutters-col-0 gutters-col-v-lg-2 gutters-col-v-md-2 gutters-col-v-2\">\n          <!---->\n          <div class=\"h-column h-column-container d-flex h-col-lg-auto h-col-md-auto h-col-auto style-11296-outer style-local-11649-c12-outer\">\n            <div data-colibri-id=\"11649-c12\" class=\"d-flex h-flex-basis h-column__inner h-px-lg-2 h-px-md-2 h-px-2 v-inner-lg-2 v-inner-md-2 v-inner-2 style-11296 style-local-11649-c12 position-relative\">\n              <!---->\n              <!---->\n              <div class=\"w-100 h-y-container h-column__content h-column__v-align flex-basis-100 align-self-lg-start align-self-md-start align-self-start\">\n                <!---->\n                <div data-colibri-id=\"11649-c13\" class=\"style-11297 style-local-11649-c13 position-relative h-element\">\n                  <!---->\n                  <div class=\"\"><!-- GMZ Cloud Server-Rechner, vollstaendig eigenstaendig, kein Upload noetig.\r\n     Der Rechner rendert in einem Shadow Root: das Theme kann sein Aussehen\r\n     nicht veraendern und er faerbt nicht auf die Seite ab.\r\n     data-permalink=\"off\" schaltet das Schreiben in die Adresszeile ab. -->\r\n<div id=\"gmz-server-rechner\" data-gmz-rechner data-permalink=\"on\"><\/div>\r\n<script>\r\n(() => {\r\n  \"use strict\";\r\n  const GMZ_STYLES = \":host {\\n  \/* Palette von gmz-it.de (Colibri-Theme), ausgelesen am 20.08.2026.\\n     colibri-1 #1f2b57 \u00b7 colibri-2 #99bbe3 \u00b7 colibri-3 #467cc6\\n     colibri-4 #e16242 \u00b7 colibri-5 #e7f0f9 \u00b7 Fliesstext #472f42\\n     Ueberschriften #47183c \u00b7 Markenblau #06509b \u00b7 Buttonblau #0a4d92 *\/\\n  --navy: #1f2b57;\\n  --blue-950: #1f2b57;\\n  --blue-800: #0a4d92;\\n  --blue-650: #06509b;\\n  --blue-100: #e7f0f9;\\n  --heading: #47183c;\\n  --ink: #472f42;\\n  --muted: #7a6674;\\n  --soft: #5f4b59;\\n  --line: #d7e2ee;\\n  --surface: #ffffff;\\n  --surface-2: #f7fafd;\\n  --canvas: #f2f7fb;\\n  \/* \\\"Immer dabei\\\" bewusst navy statt gruen: die Website kennt kein Gruen,\\n     und navy bleibt vom Auswahl-Blau unterscheidbar. *\/\\n  --success: #1f2b57;\\n  --success-bg: #eaedf4;\\n  --accent: #e16242;\\n  --warn: #c2542f;\\n  --panel-from: #1f2b57;\\n  --panel-to: #06509b;\\n  --panel-text: #ffffff;\\n  --panel-muted: #cfe0f1;\\n  --panel-dim: #b4cde4;\\n  --panel-accent: #99bbe3;\\n  --panel-line: rgb(255 255 255 \/ 15%);\\n  --panel-fill: rgb(255 255 255 \/ 8%);\\n  --panel-warn: #f0a58c;\\n  --panel-warn-soft: #f6c8ba;\\n  --glow: radial-gradient(circle at 80% 0, #e7f0f9 0, transparent 33rem);\\n  --shadow: 0 18px 50px rgb(31 43 87 \/ 10%);\\n  \/* Open Sans ist die Schrift der Website. Sie wird nicht nachgeladen: liegt\\n     sie vor, greift sie, sonst der System-Stack. *\/\\n  font-family: \\\"Open Sans\\\", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \\\"Segoe UI\\\", sans-serif;\\n  color: var(--ink);\\n  background: var(--canvas);\\n}\\n\\n* { box-sizing: border-box; }\\n.gmz-root { margin: 0; min-width: 320px; background: var(--glow), var(--canvas); }\\nbutton, input { font: inherit; color: inherit; }\\nbutton { cursor: pointer; }\\nbutton:focus-visible, input:focus-visible, summary:focus-visible, a:focus-visible { outline: 3px solid var(--accent); outline-offset: 3px; }\\n.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }\\n\\n.site-header { max-width: 1280px; margin: 0 auto; padding: 1.25rem 2rem; display: flex; align-items: center; justify-content: space-between; }\\n.brand { color: var(--blue-950); display: flex; align-items: center; gap: .85rem; text-decoration: none; font-weight: 750; letter-spacing: -.02em; }\\n.brand img { width: 150px; height: 48px; object-fit: contain; object-position: left center; border-radius: 6px; }\\n.method-badge { color: var(--blue-800); background: color-mix(in srgb, var(--surface) 75%, transparent); border: 1px solid color-mix(in srgb, var(--blue-650) 18%, transparent); border-radius: 999px; padding: .55rem .85rem; font-size: .8rem; font-weight: 700; }\\n\\nmain { max-width: 1280px; margin: 0 auto; padding: 1rem 2rem 5rem; }\\n.hero { max-width: 780px; padding: 3rem 0 2.5rem; }\\n.eyebrow, .step-label, .group-label { color: var(--blue-650); text-transform: uppercase; letter-spacing: .13em; font-size: .74rem; font-weight: 800; margin: 0 0 .65rem; }\\nh1 { color: var(--heading); font-size: clamp(2.3rem, 5vw, 4.7rem); line-height: .98; letter-spacing: -.055em; margin: 0; }\\n.hero-copy { color: var(--muted); font-size: clamp(1rem, 2vw, 1.2rem); line-height: 1.65; max-width: 700px; }\\n\\n.calculator-layout { display: grid; grid-template-columns: minmax(0, 1fr) 380px; gap: 1.5rem; align-items: start; }\\n.config-panel, .result-panel { background: var(--surface); border: 1px solid color-mix(in srgb, var(--navy) 10%, transparent); border-radius: 24px; box-shadow: var(--shadow); }\\n.panel-section { padding: clamp(1.25rem, 3vw, 2rem); }\\n.panel-section + .panel-section { border-top: 1px solid var(--line); }\\n.section-heading { display: flex; align-items: start; justify-content: space-between; gap: 1.5rem; }\\nh2 { color: var(--heading); font-size: clamp(1.25rem, 3vw, 1.75rem); letter-spacing: -.035em; margin: 0; }\\n.helper { color: var(--muted); font-size: .87rem; line-height: 1.5; margin: 1rem 0 0; }\\n.user-input-wrap { display: grid; grid-template-columns: auto 5.4rem; align-items: center; gap: .6rem; color: var(--muted); font-size: .8rem; font-weight: 700; }\\n.user-input-wrap input { width: 100%; color: var(--blue-950); background: var(--surface); border: 2px solid var(--blue-650); border-radius: 12px; padding: .68rem .7rem; text-align: center; font-size: 1.15rem; font-weight: 800; }\\n\\n.range-wrap { padding: 1rem 12px 1.25rem; }\\n#user-range { --range-progress: 4%; display: block; appearance: none; width: 100%; height: 8px; margin: 0; border-radius: 999px; background: linear-gradient(to right, var(--blue-650) 0 var(--range-progress), var(--line) var(--range-progress) 100%); }\\n#user-range::-webkit-slider-thumb { appearance: none; width: 24px; height: 24px; border: 4px solid var(--surface); border-radius: 50%; background: var(--blue-650); box-shadow: 0 0 0 2px var(--blue-650), 0 3px 9px rgb(31 43 87 \/ 25%); }\\n#user-range::-moz-range-thumb { width: 16px; height: 16px; border: 4px solid var(--surface); border-radius: 50%; background: var(--blue-650); box-shadow: 0 0 0 2px var(--blue-650), 0 3px 9px rgb(31 43 87 \/ 25%); }\\n#user-range::-moz-range-track { height: 8px; border-radius: 999px; background: transparent; }\\n.range-ticks { position: relative; height: 1.2rem; margin-top: .7rem; color: var(--muted); font-size: .7rem; }\\n.range-ticks span { position: absolute; transform: translateX(-50%); }\\n\\n.data-details { border-top: 1px solid var(--line); padding-top: 1rem; }\\n.data-details summary { color: var(--blue-800); font-size: .85rem; font-weight: 750; cursor: pointer; }\\n.data-row { display: flex; flex-wrap: wrap; align-items: center; gap: 1.2rem; margin-top: 1rem; }\\n.data-input-wrap { display: grid; grid-template-columns: auto 7rem; align-items: center; gap: .6rem; color: var(--muted); font-size: .8rem; font-weight: 700; }\\n.data-input-wrap input { width: 100%; color: var(--blue-950); background: var(--surface); border: 2px solid var(--blue-650); border-radius: 12px; padding: .5rem .6rem; text-align: center; font-size: 1rem; font-weight: 800; }\\n.data-input-wrap input:disabled { color: var(--muted); border-color: var(--line); background: var(--surface-2); }\\n.data-auto-wrap { display: flex; align-items: center; gap: .5rem; color: var(--muted); font-size: .84rem; font-weight: 700; }\\n.data-auto-wrap input { width: 1.1rem; height: 1.1rem; accent-color: var(--blue-650); }\\n\\n.selection-count { flex: 0 0 auto; color: var(--success); background: var(--success-bg); border-radius: 999px; padding: .45rem .75rem; font-size: .78rem; font-weight: 800; white-space: nowrap; }\\n.group-label { margin: 1.6rem 0 .7rem; }\\n.apps-section .group-label:first-of-type { margin-top: 1.4rem; }\\n\\n.presets, .category-filters { display: flex; flex-wrap: wrap; gap: .5rem; }\\n.presets { margin: 1.4rem 0 1rem; }\\n.presets button, .category-filters button { border: 1px solid var(--line); border-radius: 999px; background: var(--surface); color: var(--blue-800); padding: .55rem .8rem; font-size: .8rem; font-weight: 750; }\\n.presets button:hover, .category-filters button:hover, .category-filters button.active { color: var(--surface); background: var(--blue-650); border-color: var(--blue-650); }\\n.category-filters .filter-mine { color: var(--success); border-color: color-mix(in srgb, var(--success) 45%, transparent); }\\n.category-filters .filter-mine:hover, .category-filters .filter-mine.active { color: var(--surface); background: var(--success); border-color: var(--success); }\\n\\n.toggle-all { width: 100%; border: 1px dashed color-mix(in srgb, var(--blue-650) 45%, transparent); border-radius: 15px; background: var(--surface-2); color: var(--blue-800); padding: .85rem 1rem; font-size: .87rem; font-weight: 800; }\\n.toggle-all:hover { border-style: solid; background: var(--blue-100); }\\n.all-apps { margin-top: 1.2rem; }\\n.app-toolbar { display: grid; gap: .8rem; margin-bottom: 1rem; }\\n.search-field input { width: 100%; border: 1px solid var(--line); border-radius: 13px; padding: .78rem 1rem; color: var(--ink); background: var(--surface-2); }\\n.category-filters { max-height: 5.7rem; overflow: auto; padding: 2px; }\\n\\n.app-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .7rem; }\\n.app-grid--included { grid-template-columns: repeat(3, minmax(0, 1fr)); }\\n.app-card { position: relative; min-height: 150px; display: flex; flex-direction: column; align-items: flex-start; gap: .38rem; border: 1px solid var(--line); border-radius: 15px; padding: .9rem 2.3rem .9rem .9rem; color: var(--ink); background: var(--surface); text-align: left; transition: transform 120ms ease, border-color 120ms ease, background 120ms ease; }\\n.app-card:hover { transform: translateY(-2px); border-color: color-mix(in srgb, var(--blue-650) 55%, transparent); }\\n.app-card.selected { color: var(--blue-950); border-color: var(--blue-650); background: var(--blue-100); }\\n.app-card--locked { cursor: default; border-color: color-mix(in srgb, var(--success) 40%, transparent); background: var(--success-bg); }\\n.app-card--locked:hover { transform: none; border-color: color-mix(in srgb, var(--success) 40%, transparent); }\\n.app-card strong { font-size: .9rem; line-height: 1.2; }\\n.app-description { color: var(--soft); font-size: .76rem; line-height: 1.4; }\\n.app-foot { margin-top: auto; padding-top: .5rem; display: flex; flex-direction: column; align-items: flex-start; gap: .2rem; }\\n.app-card small { color: var(--muted); font-size: .68rem; font-weight: 700; letter-spacing: .03em; text-transform: uppercase; }\\n.app-metric { color: var(--muted); font-size: .68rem; font-weight: 700; font-variant-numeric: tabular-nums; }\\n.app-metric--assumed { color: var(--warn); }\\n.app-metric--soft { opacity: .72; font-weight: 600; }\\n.app-flag { color: var(--warn); border: 1px solid color-mix(in srgb, var(--warn) 45%, transparent); border-radius: 999px; padding: .1rem .45rem; font-size: .6rem; font-weight: 800; letter-spacing: .03em; }\\n.app-check { position: absolute; right: .75rem; top: .75rem; display: grid; place-items: center; width: 1.35rem; height: 1.35rem; color: var(--blue-650); border: 1px solid color-mix(in srgb, var(--blue-650) 45%, transparent); border-radius: 50%; font-size: .72rem; font-weight: 900; }\\n.selected .app-check { color: var(--surface); border-color: var(--blue-650); background: var(--blue-650); }\\n.app-check--locked { color: var(--surface); border-color: var(--success); background: var(--success); }\\n.empty-state { color: var(--muted); text-align: center; padding: 2rem; }\\n\\n.result-panel { position: sticky; top: 1rem; padding: 2rem; color: var(--panel-text); background: linear-gradient(145deg, var(--panel-from), var(--panel-to)); overflow: hidden; }\\n.result-panel::after { content: \\\"\\\"; position: absolute; width: 230px; height: 230px; right: -100px; top: -100px; border: 1px solid rgb(255 255 255 \/ 18%); border-radius: 50%; pointer-events: none; }\\n.result-panel .step-label { color: var(--panel-accent); }\\n.result-panel h2 { color: var(--panel-text); font-size: 2.1rem; }\\n.tier-label { color: var(--panel-accent); font-size: .82rem; font-weight: 750; letter-spacing: .04em; text-transform: uppercase; margin: .3rem 0 0; }\\n.tier-summary { color: var(--panel-muted); font-size: .9rem; margin: .35rem 0 1.4rem; }\\n.resource-grid { display: grid; grid-template-columns: 1fr 1fr; gap: .65rem; margin: 0; }\\n.resource-grid div { padding: .85rem; border: 1px solid var(--panel-line); border-radius: 13px; background: var(--panel-fill); }\\n.resource-grid dt { color: var(--panel-dim); font-size: .7rem; }\\n.resource-grid dd { margin: .25rem 0 0; font-size: 1.05rem; font-weight: 800; font-variant-numeric: tabular-nums; }\\n.price-row { display: flex; align-items: baseline; gap: .45rem; margin: 1.5rem 0 .4rem; }\\n.price-row span { color: var(--panel-dim); font-size: .75rem; }\\n.price-row strong { font-size: 2.1rem; letter-spacing: -.04em; }\\n.addon-note { color: var(--panel-muted); font-size: .74rem; line-height: 1.45; margin: 0 0 .8rem; }\\n\\n.notices { display: grid; gap: .5rem; margin: 1rem 0 0; }\\n.notice { color: var(--panel-warn-soft); border-left: 3px solid var(--panel-warn); padding: .1rem 0 .1rem .75rem; font-size: .78rem; line-height: 1.5; margin: 0; }\\n.notice-action { color: var(--panel-warn-soft); background: none; border: 0; padding: 0; font-size: .78rem; font-weight: 800; text-decoration: underline; }\\n\\n.method-details { margin-top: 1.25rem; padding-top: 1.1rem; border-top: 1px solid var(--panel-line); color: var(--panel-muted); font-size: .8rem; line-height: 1.5; }\\n.method-details summary { color: var(--panel-text); cursor: pointer; font-weight: 750; }\\n.method-copy { margin: .8rem 0 0; }\\n.method-link { margin: 1rem 0 0; font-size: .8rem; }\\n.method-link a, .method-details a { color: var(--panel-text); }\\n\\n.utilisation { display: grid; gap: .5rem; margin-top: 1rem; }\\n.bar { display: grid; grid-template-columns: 7.5rem 1fr 2.8rem; align-items: center; gap: .6rem; font-size: .72rem; }\\n.bar-label { color: var(--panel-dim); }\\n.bar-track { height: 7px; border-radius: 999px; background: rgb(255 255 255 \/ 14%); overflow: hidden; }\\n.bar-fill { display: block; height: 100%; border-radius: 999px; background: var(--panel-accent); transition: width 160ms ease; }\\n.bar-value { color: var(--panel-muted); text-align: right; font-variant-numeric: tabular-nums; }\\n.bar--binding .bar-fill { background: var(--panel-warn); }\\n.bar--binding .bar-label, .bar--binding .bar-value { color: var(--panel-warn-soft); font-weight: 800; }\\n.binding-note { color: var(--panel-muted); margin: .9rem 0 0; }\\n\\n.breakdown { width: 100%; margin-top: .9rem; border-collapse: collapse; font-size: .76rem; }\\n.breakdown th { color: var(--panel-muted); font-weight: 500; text-align: left; padding: .35rem 0; }\\n.breakdown td { color: var(--panel-text); text-align: right; font-weight: 700; font-variant-numeric: tabular-nums; padding: .35rem 0; white-space: nowrap; }\\n.breakdown-sum th, .breakdown-sum td { border-top: 1px solid var(--panel-line); padding-top: .5rem; color: var(--panel-text); font-weight: 800; }\\n.breakdown-reserve th, .breakdown-reserve td { color: var(--panel-dim); }\\n\\n.confidence-note { color: var(--panel-warn-soft); border-left: 3px solid var(--panel-warn); padding-left: .75rem; font-size: .78rem; line-height: 1.45; margin-top: 1.25rem; }\\n.share-row { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem; margin-top: 1.25rem; }\\n.share-button { border: 1px solid var(--panel-line); border-radius: 12px; background: var(--panel-fill); color: var(--panel-text); padding: .6rem .9rem; font-size: .8rem; font-weight: 750; }\\n.share-button:hover { background: rgb(255 255 255 \/ 14%); }\\n.share-status { color: var(--panel-accent); font-size: .72rem; }\\n.contract-note { color: var(--panel-dim); font-size: .68rem; line-height: 1.5; margin: 1.1rem 0 0; }\\n\\n@media (max-width: 1080px) {\\n  .calculator-layout { grid-template-columns: 1fr; }\\n  .result-panel { position: relative; top: auto; }\\n}\\n@media (max-width: 680px) {\\n  .site-header, main { padding-left: 1rem; padding-right: 1rem; }\\n  .method-badge { display: none; }\\n  .hero { padding-top: 2rem; }\\n  .section-heading { display: grid; }\\n  .user-input-wrap { justify-self: start; }\\n  .app-grid, .app-grid--included { grid-template-columns: repeat(2, minmax(0, 1fr)); }\\n  .bar { grid-template-columns: 6rem 1fr 2.6rem; }\\n}\\n@media (max-width: 420px) {\\n  .app-grid, .app-grid--included { grid-template-columns: 1fr; }\\n}\\n@media (prefers-reduced-motion: reduce) {\\n  *, *::before, *::after { scroll-behavior: auto !important; transition: none !important; }\\n}\\n@media print {\\n  body { background: white; }\\n  .config-panel, .presets, .toggle-all, .share-row, .site-header { display: none; }\\n  .result-panel { position: relative; top: auto; color: black; background: white; border: 1px solid #999; }\\n  .result-panel h2, .breakdown td, .breakdown-sum th { color: black; }\\n}\\n\\n:host { display: block; }\\n.gmz-root main { padding-top: 0; padding-bottom: 2.5rem; }\\n.gmz-root .hero { padding-top: 2rem; }\\n\";\r\n  const GMZ_MARKUP = \"\\n  <main id=\\\"main\\\">\\n    <section class=\\\"hero\\\" aria-labelledby=\\\"page-title\\\">\\n      <p class=\\\"eyebrow\\\">Ihre Apps. Ihre Daten. Ihr Server.<\\\/p>\\n      <h1 id=\\\"page-title\\\">Der passende Server f\u00fcr Ihr Team<\\\/h1>\\n      <p class=\\\"hero-copy\\\">Nutzerzahl und Anwendungen w\u00e4hlen. Gerechnet wird mit der GMZ-Kalkulationsgrundlage; die Empfehlung weist offen aus, welche Ressource die Gr\u00f6\u00dfe bestimmt.<\\\/p>\\n    <\\\/section>\\n\\n    <div class=\\\"calculator-layout\\\">\\n      <section class=\\\"config-panel\\\" aria-label=\\\"Server konfigurieren\\\">\\n        <div class=\\\"panel-section users-section\\\">\\n          <div class=\\\"section-heading\\\">\\n            <div>\\n              <p class=\\\"step-label\\\">Schritt 1<\\\/p>\\n              <h2>Wie viele Personen arbeiten damit?<\\\/h2>\\n            <\\\/div>\\n            <label class=\\\"user-input-wrap\\\" for=\\\"user-count\\\">\\n              <span>Nutzer<\\\/span>\\n              <input id=\\\"user-count\\\" name=\\\"user-count\\\" type=\\\"text\\\" inputmode=\\\"numeric\\\" autocomplete=\\\"off\\\" value=\\\"20\\\" aria-describedby=\\\"user-help\\\">\\n            <\\\/label>\\n          <\\\/div>\\n          <p id=\\\"user-help\\\" class=\\\"helper\\\">Zul\u00e4ssig sind 1 bis 500 Nutzer. Eingabefeld und Regler bleiben immer synchron.<\\\/p>\\n          <div class=\\\"range-wrap\\\">\\n            <input id=\\\"user-range\\\" type=\\\"range\\\" min=\\\"1\\\" max=\\\"500\\\" step=\\\"1\\\" value=\\\"20\\\" aria-label=\\\"Anzahl Nutzer\\\">\\n            <div id=\\\"range-ticks\\\" class=\\\"range-ticks\\\" aria-hidden=\\\"true\\\"><\\\/div>\\n          <\\\/div>\\n\\n          <details class=\\\"data-details\\\">\\n            <summary>Datenvolumen anpassen<\\\/summary>\\n            <div class=\\\"data-row\\\">\\n              <label class=\\\"data-input-wrap\\\" for=\\\"data-volume\\\">\\n                <span>Nutzdaten in GB<\\\/span>\\n                <input id=\\\"data-volume\\\" type=\\\"text\\\" inputmode=\\\"numeric\\\" autocomplete=\\\"off\\\" value=\\\"0\\\" aria-describedby=\\\"data-hint\\\">\\n              <\\\/label>\\n              <label class=\\\"data-auto-wrap\\\" for=\\\"data-auto\\\">\\n                <input id=\\\"data-auto\\\" type=\\\"checkbox\\\" checked>\\n                <span>automatisch sch\u00e4tzen<\\\/span>\\n              <\\\/label>\\n            <\\\/div>\\n            <p id=\\\"data-hint\\\" class=\\\"helper\\\"><\\\/p>\\n          <\\\/details>\\n        <\\\/div>\\n\\n        <div class=\\\"panel-section apps-section\\\">\\n          <div class=\\\"section-heading apps-heading\\\">\\n            <div>\\n              <p class=\\\"step-label\\\">Schritt 2<\\\/p>\\n              <h2>Welche Apps ben\u00f6tigen Sie?<\\\/h2>\\n            <\\\/div>\\n            <span id=\\\"selection-count\\\" class=\\\"selection-count\\\">0 Apps ausgew\u00e4hlt<\\\/span>\\n          <\\\/div>\\n\\n          <p class=\\\"group-label\\\">In jedem Tenant enthalten<\\\/p>\\n          <div id=\\\"included-grid\\\" class=\\\"app-grid app-grid--included\\\"><\\\/div>\\n\\n          <p class=\\\"group-label\\\">Am h\u00e4ufigsten gew\u00e4hlt<\\\/p>\\n          <div id=\\\"popular-grid\\\" class=\\\"app-grid\\\"><\\\/div>\\n\\n          <div class=\\\"presets\\\" aria-label=\\\"Schnellauswahl\\\">\\n            <button type=\\\"button\\\" data-preset=\\\"standard\\\">Standard<\\\/button>\\n            <button type=\\\"button\\\" data-preset=\\\"office\\\">B\u00fcro komplett<\\\/button>\\n            <button type=\\\"button\\\" data-preset=\\\"security\\\">Sicherheit<\\\/button>\\n            <button type=\\\"button\\\" data-preset=\\\"all\\\">Alle Apps<\\\/button>\\n            <button type=\\\"button\\\" data-preset=\\\"none\\\">Keine<\\\/button>\\n          <\\\/div>\\n\\n          <button id=\\\"toggle-all-apps\\\" class=\\\"toggle-all\\\" type=\\\"button\\\" aria-expanded=\\\"false\\\" aria-controls=\\\"all-apps\\\">Alle Apps anzeigen<\\\/button>\\n\\n          <div id=\\\"all-apps\\\" class=\\\"all-apps\\\" hidden>\\n            <div class=\\\"app-toolbar\\\">\\n              <label class=\\\"search-field\\\" for=\\\"app-search\\\">\\n                <span class=\\\"sr-only\\\">Apps durchsuchen<\\\/span>\\n                <input id=\\\"app-search\\\" type=\\\"search\\\" placeholder=\\\"App suchen ...\\\" autocomplete=\\\"off\\\">\\n              <\\\/label>\\n              <div id=\\\"category-filters\\\" class=\\\"category-filters\\\" aria-label=\\\"App-Kategorien\\\"><\\\/div>\\n            <\\\/div>\\n            <div id=\\\"app-grid\\\" class=\\\"app-grid\\\" aria-live=\\\"polite\\\"><\\\/div>\\n            <p id=\\\"empty-state\\\" class=\\\"empty-state\\\" hidden>Keine App passt zu diesem Filter.<\\\/p>\\n          <\\\/div>\\n        <\\\/div>\\n      <\\\/section>\\n\\n      <aside class=\\\"result-panel\\\" aria-labelledby=\\\"result-title\\\">\\n        <p class=\\\"step-label\\\">Ihre Empfehlung<\\\/p>\\n        <h2 id=\\\"result-title\\\">Server <span id=\\\"tier-name\\\">-<\\\/span><\\\/h2>\\n        <p class=\\\"tier-label\\\" id=\\\"tier-label\\\"><\\\/p>\\n        <p id=\\\"tier-summary\\\" class=\\\"tier-summary\\\"><\\\/p>\\n\\n        <dl class=\\\"resource-grid\\\">\\n          <div><dt>vCPU<\\\/dt><dd id=\\\"result-cpu\\\">-<\\\/dd><\\\/div>\\n          <div><dt>Arbeitsspeicher<\\\/dt><dd id=\\\"result-memory\\\">-<\\\/dd><\\\/div>\\n          <div><dt>Systemspeicher<\\\/dt><dd id=\\\"result-system-disk\\\">-<\\\/dd><\\\/div>\\n          <div><dt>Datenspeicher<\\\/dt><dd id=\\\"result-data-disk\\\">-<\\\/dd><\\\/div>\\n        <\\\/dl>\\n\\n        <div class=\\\"price-row\\\">\\n          <span>ab<\\\/span>\\n          <strong id=\\\"result-price\\\">-<\\\/strong>\\n          <span>pro Monat<\\\/span>\\n        <\\\/div>\\n        <p id=\\\"addon-note\\\" class=\\\"addon-note\\\" hidden><\\\/p>\\n\\n        <div id=\\\"notices\\\" class=\\\"notices\\\"><\\\/div>\\n\\n        <details class=\\\"method-details\\\" open>\\n          <summary>Warum diese Gr\u00f6\u00dfe?<\\\/summary>\\n          <div id=\\\"utilisation\\\" class=\\\"utilisation\\\"><\\\/div>\\n          <p id=\\\"binding-note\\\" class=\\\"binding-note\\\"><\\\/p>\\n        <\\\/details>\\n\\n        <details class=\\\"method-details\\\">\\n          <summary>Arbeitsspeicher im Detail<\\\/summary>\\n          <div id=\\\"memory-breakdown\\\"><\\\/div>\\n          <p id=\\\"rounding-note\\\" class=\\\"method-copy\\\"><\\\/p>\\n        <\\\/details>\\n\\n        <div id=\\\"confidence-note\\\" class=\\\"confidence-note\\\"><\\\/div>\\n\\n        <div class=\\\"share-row\\\">\\n          <button id=\\\"share-button\\\" class=\\\"share-button\\\" type=\\\"button\\\">Zusammenfassung kopieren<\\\/button>\\n          <span id=\\\"share-status\\\" class=\\\"share-status\\\" role=\\\"status\\\"><\\\/span>\\n        <\\\/div>\\n\\n        <p id=\\\"contract-note\\\" class=\\\"contract-note\\\"><\\\/p>\\n      <\\\/aside>\\n    <\\\/div>\\n  <\\\/main>\\n\";\r\n  \/\/ Measured resource data for the GMZ Cloud sizing calculator.\r\n  \/\/\r\n  \/\/ Two independent measurement sources feed this file:\r\n  \/\/\r\n  \/\/   \"lab\"  - sizing lab GMZ-CBA-107, 2026-07-14. Idle p95 of the complete\r\n  \/\/            compose project (app + database + sidecars), 75 samples over a\r\n  \/\/            ten minute window. See METHODOLOGY.md.\r\n  \/\/   \"prod\" - live tenants grusemann (VMID 9113) and itvs (VMID 9114),\r\n  \/\/            2026-08-20. Anonymous cgroup v2 memory (memory.stat \"anon\") per\r\n  \/\/            compose project plus a 120 s cpu.stat delta. Page cache is\r\n  \/\/            excluded on purpose: it is reclaimable and must not be sized for.\r\n  \/\/            Where both sources exist the higher value wins (\"mixed\").\r\n  \/\/   \"estimate\" - observed once during integration work, not a controlled run.\r\n  \/\/\r\n  \/\/ Values are the raw measurement rounded up to whole units. Anything that\r\n  \/\/ measured below the 5 mCPU resolution of the sampler is recorded as 5 mCPU.\r\n  \/\/\r\n  \/\/ Per-user growth and storage are NOT measured. They stay visible profile\r\n  \/\/ assumptions in app-data.js and are labelled as such in the interface.\r\n\r\n  const APP_BENCHMARKS = {\r\n    \"affine\": { cpuMilli: 62, memoryMiB: 454, source: \"lab\" },\r\n    \"bentopdf\": { cpuMilli: 5, memoryMiB: 3, source: \"prod\" },\r\n    \"ciso-assistant\": { cpuMilli: 41, memoryMiB: 1745, source: \"lab\" },\r\n    \"collabora\": { cpuMilli: 5, memoryMiB: 662, source: \"prod\" },\r\n    \"cyberchef\": { cpuMilli: 20, memoryMiB: 32, source: \"lab\" },\r\n    \"documenso\": { cpuMilli: 30, memoryMiB: 341, source: \"lab\" },\r\n    \"dolibarr\": { cpuMilli: 40, memoryMiB: 230, source: \"lab\" },\r\n    \"erpnext\": { cpuMilli: 45, memoryMiB: 580, source: \"lab\" },\r\n    \"eurooffice\": { cpuMilli: 120, memoryMiB: 2300, source: \"estimate\" },\r\n    \"frappe-hr\": { cpuMilli: 44, memoryMiB: 562, source: \"lab\" },\r\n    \"guacamole\": { cpuMilli: 30, memoryMiB: 226, source: \"lab\" },\r\n    \"huginn\": { cpuMilli: 22, memoryMiB: 827, source: \"lab\" },\r\n    \"inventree\": { cpuMilli: 97, memoryMiB: 1513, source: \"lab\" },\r\n    \"invoiceninja\": { cpuMilli: 69, memoryMiB: 1060, source: \"lab\" },\r\n    \"it-tools\": { cpuMilli: 20, memoryMiB: 32, source: \"lab\" },\r\n    \"itflow\": { cpuMilli: 41, memoryMiB: 167, source: \"lab\" },\r\n    \"joplin\": { cpuMilli: 35, memoryMiB: 296, source: \"lab\" },\r\n    \"libretranslate\": { cpuMilli: 20, memoryMiB: 251, source: \"lab\" },\r\n    \"linkwarden\": { cpuMilli: 42, memoryMiB: 775, source: \"lab\" },\r\n    \"listmonk\": { cpuMilli: 30, memoryMiB: 88, source: \"lab\" },\r\n    \"mail-archiver\": { cpuMilli: 43, memoryMiB: 132, source: \"lab\" },\r\n    \"matomo\": { cpuMilli: 39, memoryMiB: 187, source: \"lab\" },\r\n    \"mautic\": { cpuMilli: 40, memoryMiB: 168, source: \"lab\" },\r\n    \"mayan\": { cpuMilli: 64, memoryMiB: 3003, source: \"lab\" },\r\n    \"n8n\": { cpuMilli: 32, memoryMiB: 334, source: \"lab\" },\r\n    \"neonlink\": { cpuMilli: 20, memoryMiB: 42, source: \"lab\" },\r\n    \"nextcloud\": { cpuMilli: 60, memoryMiB: 212, source: \"lab\" },\r\n    \"odoo\": { cpuMilli: 28, memoryMiB: 126, source: \"lab\" },\r\n    \"paperless\": { cpuMilli: 66, memoryMiB: 813, source: \"mixed\" },\r\n    \"postiz\": { cpuMilli: 136, memoryMiB: 1304, source: \"lab\" },\r\n    \"seatsurfing\": { cpuMilli: 29, memoryMiB: 48, source: \"lab\" },\r\n    \"snipe-it\": { cpuMilli: 41, memoryMiB: 217, source: \"lab\" },\r\n    \"stirling-pdf\": { cpuMilli: 53, memoryMiB: 1044, source: \"lab\" },\r\n    \"suitecrm\": { cpuMilli: 38, memoryMiB: 171, source: \"lab\" },\r\n    \"vaultwarden\": { cpuMilli: 31, memoryMiB: 46, source: \"lab\" },\r\n    \"wiki-js\": { cpuMilli: 30, memoryMiB: 97, source: \"lab\" },\r\n    \"zammad\": { cpuMilli: 60, memoryMiB: 2363, source: \"lab\" },\r\n  };\r\n\r\n  \/\/ Everything that exists on a tenant before the first selectable app is\r\n  \/\/ deployed. Measured on 2026-08-20, worst value of both production tenants.\r\n  \/\/\r\n  \/\/   grusemann  OS\/Docker  995 MiB \/  48 mCPU   authentik  794 MiB \/  84 mCPU\r\n  \/\/   itvs       OS\/Docker 1117 MiB \/  85 mCPU   authentik  773 MiB \/  82 mCPU\r\n  \/\/   lab p95    authentik 826 MiB, branding 13 MiB\r\n  \/\/\r\n  \/\/ System disk: grusemann uses 8.4 GB with five app stacks, itvs 17.3 GB with\r\n  \/\/ nine. The 16 GiB baseline covers the operating system, Docker, the Authentik\r\n  \/\/ images and room for logs plus one image generation of rollback layers.\r\n  const PLATFORM_BASELINE = {\r\n    os: { cpuMilli: 85, memoryMiB: 1120, label: \"Betriebssystem, Docker, Monitoring\" },\r\n    authentik: { cpuMilli: 90, memoryMiB: 830, label: \"Authentik Single Sign-on\" },\r\n    branding: { cpuMilli: 5, memoryMiB: 15, label: \"Branding-Dienst\" },\r\n    systemDiskGiB: 16,\r\n    dataDiskGiB: 5,\r\n    source: \"prod\",\r\n  };\r\n\r\n  const BENCHMARK_META = {\r\n    lab: {\r\n      environment: \"GMZ-CBA-107\",\r\n      measuredAt: \"2026-07-14T12:15:52Z\",\r\n      method: \"idle p95 je Compose-Projekt, 75 Stichproben\",\r\n    },\r\n    prod: {\r\n      environment: \"grusemann (9113), itvs (9114)\",\r\n      measuredAt: \"2026-08-20T11:15:00Z\",\r\n      method: \"cgroup v2 anon-RAM je Compose-Projekt, cpu.stat-Delta \u00fcber 120 s\",\r\n    },\r\n    \/\/ The newest date that any number in this file carries.\r\n    measuredAt: \"2026-08-20T11:15:00Z\",\r\n  };\r\n\r\n  \/\/ Values and rules of the original GMZ VM-Kalkulator (GMZ_VM_Kalkulator.xlsm,\r\n  \/\/ sheets \"Apps\" and \"Preise\"). This is the basis the recommendation is\r\n  \/\/ calculated from.\r\n  \/\/\r\n  \/\/ The workbook states every app resource \"pro 10 Nutzer\" and scales it\r\n  \/\/ linearly: need = value \/ 10 * users + per-VM base. Database services are\r\n  \/\/ added ONCE per database type across the whole selection, not per app - that\r\n  \/\/ is what SUMPRODUCT((AppSelect=\"x\")*ISNUMBER(SEARCH(\"postgres\",...)))>0 does\r\n  \/\/ in the sheet, and it is reproduced here unchanged.\r\n\r\n  \/** Sheet \"Preise\" A11: fixed reserve per VM, independent of users and apps. *\/\r\n  const LEGACY_BASE = { cpu: 0.5, memoryGiB: 0.5, systemDiskGiB: 8, dataDiskGiB: 0 };\r\n\r\n  \/** Sheet \"Preise\" A15:A17, per 10 users, charged once per database type. *\/\r\n  const LEGACY_DATABASES = {\r\n    postgres: { label: \"PostgreSQL\", cpu: 0.3, memoryMiB: 512, systemDiskGiB: 3, dataDiskGiB: 0 },\r\n    redis: { label: \"Redis\", cpu: 0.1, memoryMiB: 128, systemDiskGiB: 0.1, dataDiskGiB: 0 },\r\n    mariadb: { label: \"MariaDB\", cpu: 0.3, memoryMiB: 512, systemDiskGiB: 2, dataDiskGiB: 0 },\r\n  };\r\n\r\n  \/**\r\n   * Sheet \"Apps\" B4:E12, verbatim, per 10 users. The workbook knew nine apps;\r\n   * Authentik is handled separately because it is not selectable any more.\r\n   *\/\r\n  const LEGACY_APPS = {\r\n    nextcloud: { cpu: 0.5, memoryMiB: 512, systemDiskGiB: 2, dataDiskGiB: 80 },\r\n    vaultwarden: { cpu: 0.1, memoryMiB: 96, systemDiskGiB: 0.5, dataDiskGiB: 0 },\r\n    paperless: { cpu: 0.5, memoryMiB: 512, systemDiskGiB: 2, dataDiskGiB: 20 },\r\n    n8n: { cpu: 0.3, memoryMiB: 320, systemDiskGiB: 1, dataDiskGiB: 0 },\r\n    \"stirling-pdf\": { cpu: 0.3, memoryMiB: 384, systemDiskGiB: 0.5, dataDiskGiB: 0 },\r\n    documenso: { cpu: 0.2, memoryMiB: 256, systemDiskGiB: 1, dataDiskGiB: 5 },\r\n    \"it-tools\": { cpu: 0.05, memoryMiB: 64, systemDiskGiB: 0.1, dataDiskGiB: 0 },\r\n    libretranslate: { cpu: 0.5, memoryMiB: 768, systemDiskGiB: 3, dataDiskGiB: 0 },\r\n  };\r\n\r\n  \/** Sheet \"Apps\" A4, per 10 users. Runs in every tenant, so it is never optional. *\/\r\n  const LEGACY_AUTHENTIK = { cpu: 0.5, memoryMiB: 768, systemDiskGiB: 2, dataDiskGiB: 0, databases: [\"postgres\", \"redis\"] };\r\n\r\n  \/**\r\n   * The workbook covered nine of today's 37 apps. The remaining 28 are filled\r\n   * from its own three magnitude classes rather than from a new invention:\r\n   *\r\n   *   light  = IT-Tools                                     0,05 \/ 64\r\n   *   medium = n8n, Stirling PDF, Documenso                  0,3 \/ 320\r\n   *   heavy  = Authentik, Nextcloud, Paperless, LibreTranslate 0,5 \/ 640\r\n   *\r\n   * Memory is the mean of the workbook rows in each class, disks are their\r\n   * median. Apps listed in LEGACY_APPS always win over this table.\r\n   *\/\r\n  const LEGACY_PROFILES = {\r\n    light: { cpu: 0.05, memoryMiB: 64, systemDiskGiB: 0.1, dataDiskGiB: 0 },\r\n    medium: { cpu: 0.3, memoryMiB: 320, systemDiskGiB: 1, dataDiskGiB: 2 },\r\n    heavy: { cpu: 0.5, memoryMiB: 640, systemDiskGiB: 2, dataDiskGiB: 20 },\r\n  };\r\n\r\n  \/**\r\n   * Which database containers each app actually starts, read from\r\n   * compose\/<app>\/docker-compose.yml on 2026-08-20. The workbook carried this\r\n   * only for its nine apps and got Nextcloud wrong (it says MariaDB, the stack\r\n   * runs PostgreSQL). Both cost the same in the price sheet, so the correction\r\n   * only moves the system disk by one gigabyte.\r\n   *\/\r\n  const LEGACY_APP_DATABASES = {\r\n    affine: [\"postgres\", \"redis\"],\r\n    authentik: [\"postgres\", \"redis\"],\r\n    documenso: [\"postgres\"],\r\n    dolibarr: [\"mariadb\"],\r\n    erpnext: [\"mariadb\", \"redis\"],\r\n    \"frappe-hr\": [\"mariadb\", \"redis\"],\r\n    guacamole: [\"postgres\"],\r\n    inventree: [\"postgres\", \"redis\"],\r\n    invoiceninja: [\"mariadb\", \"redis\"],\r\n    itflow: [\"mariadb\"],\r\n    joplin: [\"postgres\"],\r\n    linkwarden: [\"postgres\"],\r\n    listmonk: [\"postgres\"],\r\n    \"mail-archiver\": [\"postgres\"],\r\n    matomo: [\"mariadb\"],\r\n    mautic: [\"mariadb\"],\r\n    mayan: [\"postgres\", \"redis\"],\r\n    n8n: [\"postgres\"],\r\n    nextcloud: [\"postgres\", \"redis\"],\r\n    odoo: [\"postgres\"],\r\n    paperless: [\"postgres\", \"redis\"],\r\n    postiz: [\"postgres\", \"redis\"],\r\n    seatsurfing: [\"postgres\"],\r\n    \"snipe-it\": [\"mariadb\"],\r\n    suitecrm: [\"mariadb\"],\r\n    vaultwarden: [\"postgres\"],\r\n    \"wiki-js\": [\"postgres\"],\r\n    zammad: [\"postgres\", \"redis\"],\r\n  };\r\n\r\n  const LEGACY_META = {\r\n    source: \"GMZ_VM_Kalkulator.xlsm\",\r\n    sheets: [\"Apps\", \"Preise\"],\r\n    scale: \"Werte je 10 Nutzer, linear hochgerechnet\",\r\n  };\r\n\r\n\r\n\r\n  \/\/ The catalog mirrors compose\/*\/ in the platform repository minus the two\r\n  \/\/ projects that every tenant always gets: authentik and branding. Those two\r\n  \/\/ are part of PLATFORM_BASELINE instead, because they are not optional.\r\n  const CATALOG = [\r\n    { id: \"affine\", name: \"AFFiNE\", category: \"Zusammenarbeit\", description: \"Arbeitsbereich f\u00fcr Dokumente, Whiteboards und gemeinsames Wissen.\", profile: \"medium\" },\r\n    { id: \"bentopdf\", name: \"BentoPDF\", category: \"Dokumente\", description: \"PDF-Dateien direkt im Browser zusammenf\u00fchren, teilen und drehen.\", profile: \"light\", popular: true },\r\n    { id: \"ciso-assistant\", name: \"CISO Assistant\", category: \"Sicherheit\", description: \"Informationssicherheit, Risiken und Compliance zentral verwalten.\", profile: \"medium\" },\r\n    { id: \"collabora\", name: \"Collabora Office\", category: \"Zusammenarbeit\", description: \"Office-Dokumente gemeinsam direkt in Nextcloud bearbeiten.\", profile: \"medium\", requires: [\"nextcloud\"], popular: true },\r\n    { id: \"cyberchef\", name: \"CyberChef\", category: \"Werkzeuge\", description: \"Daten im Browser analysieren, dekodieren und transformieren.\", profile: \"light\" },\r\n    { id: \"documenso\", name: \"Documenso\", category: \"Dokumente\", description: \"Dokumente digital versenden, unterschreiben und nachverfolgen.\", profile: \"medium\", popular: true },\r\n    { id: \"dolibarr\", name: \"Dolibarr\", category: \"Gesch\u00e4ft\", description: \"Modulare ERP- und CRM-L\u00f6sung f\u00fcr kleine und mittlere Unternehmen.\", profile: \"medium\" },\r\n    { id: \"erpnext\", name: \"ERPNext\", category: \"Gesch\u00e4ft\", description: \"Integriertes ERP f\u00fcr Finanzen, Warenwirtschaft und Vertrieb.\", profile: \"heavy\" },\r\n    { id: \"eurooffice\", name: \"Euro-Office\", category: \"Zusammenarbeit\", description: \"Word-, Excel- und PowerPoint-Dateien in Nextcloud bearbeiten.\", profile: \"heavy\", requires: [\"nextcloud\"] },\r\n    { id: \"frappe-hr\", name: \"Frappe HR\", category: \"Gesch\u00e4ft\", description: \"Personal, Abwesenheiten, Arbeitszeiten und Prozesse organisieren.\", profile: \"heavy\" },\r\n    { id: \"guacamole\", name: \"Apache Guacamole\", category: \"IT\", description: \"Sicherer Browserzugriff auf entfernte Desktops und Server.\", profile: \"medium\" },\r\n    { id: \"huginn\", name: \"Huginn\", category: \"Automatisierung\", description: \"Eigene Agenten beobachten Ereignisse und automatisieren Abl\u00e4ufe.\", profile: \"medium\" },\r\n    { id: \"inventree\", name: \"InvenTree\", category: \"Gesch\u00e4ft\", description: \"Best\u00e4nde, Bauteile, Lieferanten und Fertigung verwalten.\", profile: \"medium\" },\r\n    { id: \"invoiceninja\", name: \"Invoice Ninja\", category: \"Gesch\u00e4ft\", description: \"Angebote, Rechnungen, Zahlungen und Kunden verwalten.\", profile: \"medium\" },\r\n    { id: \"it-tools\", name: \"IT-Tools\", category: \"Werkzeuge\", description: \"Praktische Webwerkzeuge f\u00fcr Entwicklung und Administration.\", profile: \"light\" },\r\n    { id: \"itflow\", name: \"ITFlow\", category: \"IT\", description: \"IT-Dokumentation, Tickets, Kunden und Abrechnung verbinden.\", profile: \"medium\" },\r\n    { id: \"joplin\", name: \"Joplin Server\", category: \"Zusammenarbeit\", description: \"Notizen und Aufgaben sicher zwischen Joplin-Ger\u00e4ten synchronisieren.\", profile: \"medium\" },\r\n    { id: \"libretranslate\", name: \"LibreTranslate\", category: \"Werkzeuge\", description: \"Texte automatisiert und datenschutzfreundlich \u00fcbersetzen.\", profile: \"heavy\" },\r\n    { id: \"linkwarden\", name: \"Linkwarden\", category: \"Zusammenarbeit\", description: \"Lesezeichen gemeinsam sammeln, ordnen und dauerhaft archivieren.\", profile: \"medium\" },\r\n    { id: \"listmonk\", name: \"Listmonk\", category: \"Marketing\", description: \"Newsletter und gro\u00dfe Verteilerlisten effizient betreiben.\", profile: \"medium\" },\r\n    { id: \"mail-archiver\", name: \"Mail Archiver\", category: \"Dokumente\", description: \"E-Mails zentral, nachvollziehbar und durchsuchbar archivieren.\", profile: \"medium\" },\r\n    { id: \"matomo\", name: \"Matomo\", category: \"Marketing\", description: \"Webanalyse mit voller Kontrolle \u00fcber Besucher- und Nutzungsdaten.\", profile: \"medium\" },\r\n    { id: \"mautic\", name: \"Mautic\", category: \"Marketing\", description: \"Kampagnen, Kontakte und Marketing-Automatisierung steuern.\", profile: \"heavy\" },\r\n    { id: \"mayan\", name: \"Mayan EDMS\", category: \"Dokumente\", description: \"Dokumente mit Versionen, Metadaten und Freigaben verwalten.\", profile: \"heavy\" },\r\n    { id: \"n8n\", name: \"n8n\", category: \"Automatisierung\", description: \"Anwendungen und Daten \u00fcber visuelle Workflows automatisieren.\", profile: \"medium\" },\r\n    { id: \"neonlink\", name: \"NeonLink\", category: \"Zusammenarbeit\", description: \"Links und h\u00e4ufig genutzte Dienste auf einer Startseite b\u00fcndeln.\", profile: \"light\" },\r\n    { id: \"nextcloud\", name: \"Nextcloud\", category: \"Zusammenarbeit\", description: \"Dateien, Kalender, Kontakte und Teamarbeit aus einer Cloud.\", profile: \"heavy\", popular: true },\r\n    { id: \"odoo\", name: \"Odoo\", category: \"Gesch\u00e4ft\", description: \"Gesch\u00e4ftsprozesse von CRM bis Buchhaltung modular abbilden.\", profile: \"heavy\" },\r\n    { id: \"paperless\", name: \"Paperless-ngx\", category: \"Dokumente\", description: \"Papierdokumente per OCR erfassen, ordnen und wiederfinden.\", profile: \"heavy\", popular: true },\r\n    { id: \"postiz\", name: \"Postiz\", category: \"Marketing\", description: \"Beitr\u00e4ge f\u00fcr soziale Netzwerke planen und ver\u00f6ffentlichen.\", profile: \"heavy\" },\r\n    { id: \"seatsurfing\", name: \"SeatSurfing\", category: \"Gesch\u00e4ft\", description: \"Arbeitspl\u00e4tze und B\u00fcroressourcen flexibel reservieren.\", profile: \"medium\" },\r\n    { id: \"snipe-it\", name: \"Snipe-IT\", category: \"IT\", description: \"IT-Ger\u00e4te, Zubeh\u00f6r, Lizenzen und Ausgaben nachverfolgen.\", profile: \"medium\" },\r\n    { id: \"stirling-pdf\", name: \"Stirling PDF\", category: \"Dokumente\", description: \"PDF-Werkzeuge auf dem Server; im Katalog von BentoPDF abgel\u00f6st.\", profile: \"medium\", supersededBy: \"bentopdf\" },\r\n    { id: \"suitecrm\", name: \"SuiteCRM\", category: \"Gesch\u00e4ft\", description: \"Kundenbeziehungen, Vertrieb und Serviceprozesse organisieren.\", profile: \"heavy\" },\r\n    { id: \"vaultwarden\", name: \"Vaultwarden\", category: \"Sicherheit\", description: \"Passw\u00f6rter und Zugangsdaten sicher im Team teilen.\", profile: \"medium\", popular: true },\r\n    { id: \"wiki-js\", name: \"Wiki.js\", category: \"Zusammenarbeit\", description: \"Modernes Wiki f\u00fcr Dokumentation und gemeinsames Wissen.\", profile: \"medium\" },\r\n    { id: \"zammad\", name: \"Zammad\", category: \"IT\", description: \"Helpdesk f\u00fcr Tickets, Kundenanfragen und Supportkan\u00e4le.\", profile: \"heavy\" },\r\n  ];\r\n\r\n  const APP_CATALOG = CATALOG.map((app) => ({\r\n    ...app,\r\n    popular: app.popular === true,\r\n    benchmark: APP_BENCHMARKS[app.id] ?? null,\r\n    \/\/ CPU and memory come from the lab or from production where available.\r\n    \/\/ Per-user growth and storage stay profile assumptions for every app.\r\n    source: APP_BENCHMARKS[app.id]?.source ?? \"profile\",\r\n  }));\r\n\r\n  \/\/ Fixed footprint per app plus the part that grows with the number of users.\r\n  \/\/ Only the fixed CPU\/RAM part gets replaced by a measurement; the growth terms\r\n  \/\/ and both storage terms are deliberate, conservative, labelled assumptions.\r\n  const PROFILE_DEFAULTS = {\r\n    light: { cpuMilli: 120, memoryMiB: 256, systemDiskGiB: 0.5, dataDiskGiB: 1, cpuPerUserMilli: 2, memoryPerUserMiB: 2, dataPerUserGiB: 0.01 },\r\n    medium: { cpuMilli: 300, memoryMiB: 768, systemDiskGiB: 2, dataDiskGiB: 2, cpuPerUserMilli: 5, memoryPerUserMiB: 5, dataPerUserGiB: 0.03 },\r\n    heavy: { cpuMilli: 650, memoryMiB: 1536, systemDiskGiB: 4, dataDiskGiB: 4, cpuPerUserMilli: 10, memoryPerUserMiB: 10, dataPerUserGiB: 0.08 },\r\n  };\r\n\r\n  const PROFILE_LABELS = { light: \"leicht\", medium: \"mittel\", heavy: \"schwer\" };\r\n\r\n  const SOURCE_LABELS = {\r\n    lab: \"im Sizing-Lab gemessen\",\r\n    prod: \"auf einem Produktivsystem gemessen\",\r\n    mixed: \"gemessen (Lab und Produktivsystem)\",\r\n    estimate: \"gesch\u00e4tzt, noch nicht vermessen\",\r\n    profile: \"Profilannahme, noch nicht vermessen\",\r\n  };\r\n\r\n  const SERVER_TIERS = [\r\n    { name: \"S\", label: \"Starter\", cpu: 2, memoryGiB: 4, systemDiskGiB: 50, dataDiskGiB: 500, monthlyEuro: 59 },\r\n    { name: \"M\", label: \"Business\", cpu: 4, memoryGiB: 8, systemDiskGiB: 100, dataDiskGiB: 1000, monthlyEuro: 99 },\r\n    { name: \"L\", label: \"Professional\", cpu: 8, memoryGiB: 16, systemDiskGiB: 150, dataDiskGiB: 1500, monthlyEuro: 129 },\r\n    { name: \"XL\", label: \"Enterprise\", cpu: 16, memoryGiB: 32, systemDiskGiB: 200, dataDiskGiB: 2000, monthlyEuro: 179 },\r\n  ];\r\n\r\n  const ADDON_PRICES = {\r\n    cpuPerUnit: 8,\r\n    memoryPerGiB: 5,\r\n    systemDiskPer50GiB: 9,\r\n    dataDiskPer500GiB: 45,\r\n  };\r\n\r\n  const CONTRACT_TERMS = {\r\n    monthlyPaymentSurchargeEuro: 10,\r\n    text: \"Vertragslaufzeit 12 Monate \u00b7 j\u00e4hrliche Zahlung im Voraus \u00b7 monatliche Zahlung +10,00 \u20ac\/Monat \u00b7 Zahlungsziel 7 Tage nach Bereitstellung.\",\r\n  };\r\n\r\n  \/\/ Order of the six headline tiles. Every id must carry popular: true.\r\n  const POPULAR_ORDER = [\"nextcloud\", \"vaultwarden\", \"paperless\", \"documenso\", \"bentopdf\", \"collabora\"];\r\n\r\n  const PRESETS = {\r\n    standard: { label: \"Standard\", apps: [\"nextcloud\", \"vaultwarden\"] },\r\n    office: { label: \"B\u00fcro komplett\", apps: [\"nextcloud\", \"collabora\", \"bentopdf\", \"documenso\", \"paperless\", \"wiki-js\"] },\r\n    security: { label: \"Sicherheit\", apps: [\"vaultwarden\", \"ciso-assistant\", \"snipe-it\", \"mail-archiver\", \"cyberchef\"] },\r\n  };\r\n\r\n  \/\/ Selection the calculator starts with. Authentik is always on top of this and\r\n  \/\/ is not selectable, so the visible start state is Authentik, Nextcloud and\r\n  \/\/ Vaultwarden - the combination every GMZ tenant actually gets.\r\n  const DEFAULT_SELECTION = PRESETS.standard.apps;\r\n\r\n\r\n  const USER_LIMITS = Object.freeze({ min: 1, max: 500, initial: 20 });\r\n  const DATA_LIMITS = Object.freeze({ min: 0, max: 20000 });\r\n\r\n  \/**\r\n   * Which model produces the recommendation.\r\n   *\r\n   *   \"legacy\"   - the values and formula of GMZ_VM_Kalkulator.xlsm. Active.\r\n   *   \"measured\" - sizing lab plus production measurement, see METHODOLOGY.md.\r\n   *\r\n   * Both models are kept and tested. Switching is this one constant; the\r\n   * interface reads everything else off the result object.\r\n   *\/\r\n  const SIZING_MODEL = \"legacy\";\r\n\r\n  const MODEL_LABELS = {\r\n    legacy: `Werten des GMZ VM-Kalkulators (${LEGACY_META.scale})`,\r\n    measured: \"gemessener Grundlast plus Betriebsreserve\",\r\n  };\r\n\r\n  \/* Constants of the measured model. Not used while SIZING_MODEL is \"legacy\". *\/\r\n  const WORKLOAD_HEADROOM = 1.35;\r\n  const FREE_MEMORY_FLOOR_MIB = 512;\r\n  const SYSTEM_DISK_HEADROOM = 1.3;\r\n  const DATA_DISK_HEADROOM = 1.25;\r\n\r\n  const RESOURCE_LABELS = {\r\n    cpu: \"vCPU\",\r\n    memoryGiB: \"Arbeitsspeicher\",\r\n    systemDiskGiB: \"Systemspeicher\",\r\n    dataDiskGiB: \"Datenspeicher\",\r\n  };\r\n\r\n  const TIER_KEYS = [\"cpu\", \"memoryGiB\", \"systemDiskGiB\", \"dataDiskGiB\"];\r\n\r\n  \/** Excel CEILING(value, step): always up, never down. *\/\r\n  function ceilTo(value, step) {\r\n    return Math.ceil(value \/ step - 1e-9) * step;\r\n  }\r\n\r\n  function normalizeUsers(value, fallback = USER_LIMITS.min) {\r\n    const digits = String(value ?? \"\").replace(\/\\D\/g, \"\");\r\n    if (!digits) return fallback;\r\n    return Math.min(USER_LIMITS.max, Math.max(USER_LIMITS.min, Number.parseInt(digits, 10)));\r\n  }\r\n\r\n  function normalizeDataVolume(value) {\r\n    if (value === null || value === undefined || value === \"\" || value === \"auto\") return null;\r\n    const digits = String(value).replace(\/\\D\/g, \"\");\r\n    if (!digits) return null;\r\n    return Math.min(DATA_LIMITS.max, Math.max(DATA_LIMITS.min, Number.parseInt(digits, 10)));\r\n  }\r\n\r\n  function orderAppsBySelection(apps, selectedIds) {\r\n    const catalogOrder = new Map(APP_CATALOG.map((app, index) => [app.id, index]));\r\n    return [...apps].sort((left, right) => {\r\n      const selectionOrder = Number(selectedIds.has(right.id)) - Number(selectedIds.has(left.id));\r\n      return selectionOrder || catalogOrder.get(left.id) - catalogOrder.get(right.id);\r\n    });\r\n  }\r\n\r\n  \/* ------------------------------------------------------------------ *\/\r\n  \/* Legacy model - GMZ_VM_Kalkulator.xlsm                               *\/\r\n  \/* ------------------------------------------------------------------ *\/\r\n\r\n  \/** Per-10-user row for one app: the workbook value, else its magnitude class. *\/\r\n  function legacyResourcesForApp(app) {\r\n    const verbatim = LEGACY_APPS[app.id];\r\n    return { ...(verbatim ?? LEGACY_PROFILES[app.profile]), fromWorkbook: Boolean(verbatim) };\r\n  }\r\n\r\n  \/** Database types the selection needs. Authentik always contributes. *\/\r\n  function legacyDatabasesFor(selected) {\r\n    const types = new Set(LEGACY_AUTHENTIK.databases);\r\n    selected.forEach((app) => (LEGACY_APP_DATABASES[app.id] ?? []).forEach((type) => types.add(type)));\r\n    return [\"postgres\", \"redis\", \"mariadb\"].filter((type) => types.has(type));\r\n  }\r\n\r\n  function calculateLegacySizing({ users, selected, dataVolumeGiB }) {\r\n    const rows = selected.map((app) => legacyResourcesForApp(app));\r\n    const sum = (list, key) => list.reduce((total, entry) => total + entry[key], 0);\r\n    const apps = {\r\n      cpu: sum(rows, \"cpu\"),\r\n      memoryMiB: sum(rows, \"memoryMiB\"),\r\n      systemDiskGiB: sum(rows, \"systemDiskGiB\"),\r\n      dataDiskGiB: sum(rows, \"dataDiskGiB\"),\r\n    };\r\n\r\n    \/\/ Database services are charged once per type across the whole selection,\r\n    \/\/ exactly as the workbook does it.\r\n    const databaseTypes = legacyDatabasesFor(selected);\r\n    const databaseRows = databaseTypes.map((type) => LEGACY_DATABASES[type]);\r\n    const databases = {\r\n      cpu: sum(databaseRows, \"cpu\"),\r\n      memoryMiB: sum(databaseRows, \"memoryMiB\"),\r\n      systemDiskGiB: sum(databaseRows, \"systemDiskGiB\"),\r\n      dataDiskGiB: sum(databaseRows, \"dataDiskGiB\"),\r\n    };\r\n\r\n    const share = users \/ 10;\r\n    const scaled = (key) => (LEGACY_AUTHENTIK[key] + apps[key] + databases[key]) * share;\r\n    const payloadGiB = dataVolumeGiB === null ? scaled(\"dataDiskGiB\") : dataVolumeGiB;\r\n\r\n    const raw = {\r\n      cpu: scaled(\"cpu\") + LEGACY_BASE.cpu,\r\n      memoryGiB: scaled(\"memoryMiB\") \/ 1024 + LEGACY_BASE.memoryGiB,\r\n      systemDiskGiB: scaled(\"systemDiskGiB\") + LEGACY_BASE.systemDiskGiB,\r\n      dataDiskGiB: payloadGiB + LEGACY_BASE.dataDiskGiB,\r\n    };\r\n\r\n    const required = {\r\n      cpu: Math.ceil(raw.cpu - 1e-9),\r\n      memoryGiB: ceilTo(raw.memoryGiB, 0.5),\r\n      systemDiskGiB: ceilTo(raw.systemDiskGiB, 5),\r\n      dataDiskGiB: ceilTo(raw.dataDiskGiB, 5),\r\n    };\r\n\r\n    const databaseLabel = databaseTypes.map((type) => LEGACY_DATABASES[type].label).join(\", \");\r\n    return {\r\n      model: \"legacy\",\r\n      raw,\r\n      required,\r\n      payloadGiB,\r\n      perTenUsers: { apps, databases },\r\n      databaseTypes,\r\n      databaseLabel,\r\n      memoryBreakdown: [\r\n        { label: \"Grundbedarf pro VM\", value: LEGACY_BASE.memoryGiB * 1024, kind: \"platform\" },\r\n        { label: \"Authentik\", value: LEGACY_AUTHENTIK.memoryMiB * share, kind: \"platform\" },\r\n        { label: `Backend-Dienste: ${databaseLabel}`, value: databases.memoryMiB * share, kind: \"platform\" },\r\n        { label: `${selected.length} ${selected.length === 1 ? \"gew\u00e4hlte App\" : \"gew\u00e4hlte Apps\"}`, value: apps.memoryMiB * share, kind: \"apps\" },\r\n      ],\r\n      memoryTotalMiB: raw.memoryGiB * 1024,\r\n      workbookCount: rows.filter((row) => row.fromWorkbook).length,\r\n    };\r\n  }\r\n\r\n  \/* ------------------------------------------------------------------ *\/\r\n  \/* Measured model - sizing lab plus production                         *\/\r\n  \/* ------------------------------------------------------------------ *\/\r\n\r\n  function resourcesForApp(app, users) {\r\n    const profile = PROFILE_DEFAULTS[app.profile];\r\n    const measured = app.benchmark;\r\n    return {\r\n      cpuMilli: (measured?.cpuMilli ?? profile.cpuMilli) + profile.cpuPerUserMilli * users,\r\n      memoryMiB: (measured?.memoryMiB ?? profile.memoryMiB) + profile.memoryPerUserMiB * users,\r\n      systemDiskGiB: profile.systemDiskGiB,\r\n      dataDiskGiB: profile.dataDiskGiB,\r\n      baseCpuMilli: measured?.cpuMilli ?? profile.cpuMilli,\r\n      baseMemoryMiB: measured?.memoryMiB ?? profile.memoryMiB,\r\n      source: app.source,\r\n    };\r\n  }\r\n\r\n  function platformTotals() {\r\n    const { os, authentik, branding } = PLATFORM_BASELINE;\r\n    return {\r\n      cpuMilli: os.cpuMilli + authentik.cpuMilli + branding.cpuMilli,\r\n      memoryMiB: os.memoryMiB + authentik.memoryMiB + branding.memoryMiB,\r\n      systemDiskGiB: PLATFORM_BASELINE.systemDiskGiB,\r\n      dataDiskGiB: PLATFORM_BASELINE.dataDiskGiB,\r\n    };\r\n  }\r\n\r\n  function calculateMeasuredSizing({ users, selected, dataVolumeGiB }) {\r\n    const platform = platformTotals();\r\n    const rows = selected.map((app) => resourcesForApp(app, users));\r\n    const apps = rows.reduce((total, row) => ({\r\n      cpuMilli: total.cpuMilli + row.cpuMilli,\r\n      memoryMiB: total.memoryMiB + row.memoryMiB,\r\n      systemDiskGiB: total.systemDiskGiB + row.systemDiskGiB,\r\n      dataDiskGiB: total.dataDiskGiB + row.dataDiskGiB,\r\n    }), { cpuMilli: 0, memoryMiB: 0, systemDiskGiB: 0, dataDiskGiB: 0 });\r\n\r\n    const autoPayload = selected.reduce((total, app) => total + PROFILE_DEFAULTS[app.profile].dataPerUserGiB, 0) * users;\r\n    const payloadGiB = dataVolumeGiB === null ? Math.round(autoPayload) : dataVolumeGiB;\r\n\r\n    const rawMemoryMiB = platform.memoryMiB + apps.memoryMiB * WORKLOAD_HEADROOM + FREE_MEMORY_FLOOR_MIB;\r\n    const raw = {\r\n      cpu: (platform.cpuMilli + apps.cpuMilli * WORKLOAD_HEADROOM) \/ 1000,\r\n      memoryGiB: rawMemoryMiB \/ 1024,\r\n      systemDiskGiB: (platform.systemDiskGiB + apps.systemDiskGiB) * SYSTEM_DISK_HEADROOM,\r\n      dataDiskGiB: (platform.dataDiskGiB + apps.dataDiskGiB + payloadGiB) * DATA_DISK_HEADROOM,\r\n    };\r\n\r\n    return {\r\n      model: \"measured\",\r\n      raw,\r\n      payloadGiB,\r\n      apps,\r\n      platform,\r\n      required: {\r\n        cpu: Math.max(SERVER_TIERS[0].cpu, Math.ceil(raw.cpu)),\r\n        memoryGiB: Math.max(SERVER_TIERS[0].memoryGiB, Math.ceil(raw.memoryGiB)),\r\n        systemDiskGiB: Math.max(SERVER_TIERS[0].systemDiskGiB, Math.ceil(raw.systemDiskGiB)),\r\n        dataDiskGiB: Math.max(SERVER_TIERS[0].dataDiskGiB, Math.ceil(raw.dataDiskGiB)),\r\n      },\r\n      memoryBreakdown: [\r\n        { label: PLATFORM_BASELINE.os.label, value: PLATFORM_BASELINE.os.memoryMiB, kind: \"platform\" },\r\n        { label: PLATFORM_BASELINE.authentik.label, value: PLATFORM_BASELINE.authentik.memoryMiB, kind: \"platform\" },\r\n        { label: PLATFORM_BASELINE.branding.label, value: PLATFORM_BASELINE.branding.memoryMiB, kind: \"platform\" },\r\n        { label: `${selected.length} gew\u00e4hlte Apps`, value: apps.memoryMiB, kind: \"apps\" },\r\n        { label: `Betriebsreserve (${Math.round((WORKLOAD_HEADROOM - 1) * 100)} % auf die Apps)`, value: apps.memoryMiB * (WORKLOAD_HEADROOM - 1), kind: \"reserve\" },\r\n        { label: \"Freier Speicher f\u00fcr Cache und Spitzen\", value: FREE_MEMORY_FLOOR_MIB, kind: \"reserve\" },\r\n      ],\r\n      memoryTotalMiB: rawMemoryMiB,\r\n    };\r\n  }\r\n\r\n  \/* ------------------------------------------------------------------ *\/\r\n  \/* Shared: tier selection                                              *\/\r\n  \/* ------------------------------------------------------------------ *\/\r\n\r\n  function extendedTier(required) {\r\n    const base = SERVER_TIERS.at(-1);\r\n    const cpuAddon = Math.max(0, required.cpu - base.cpu);\r\n    const memoryAddon = Math.max(0, ceilTo(Math.max(0, required.memoryGiB - base.memoryGiB), 1));\r\n    const systemDiskAddon = required.systemDiskGiB > base.systemDiskGiB ? ceilTo(required.systemDiskGiB - base.systemDiskGiB, 50) : 0;\r\n    const dataDiskAddon = required.dataDiskGiB > base.dataDiskGiB ? ceilTo(required.dataDiskGiB - base.dataDiskGiB, 500) : 0;\r\n    return {\r\n      name: \"XL+\",\r\n      label: \"Enterprise plus\",\r\n      cpu: base.cpu + cpuAddon,\r\n      memoryGiB: base.memoryGiB + memoryAddon,\r\n      systemDiskGiB: base.systemDiskGiB + systemDiskAddon,\r\n      dataDiskGiB: base.dataDiskGiB + dataDiskAddon,\r\n      monthlyEuro: base.monthlyEuro\r\n        + cpuAddon * ADDON_PRICES.cpuPerUnit\r\n        + memoryAddon * ADDON_PRICES.memoryPerGiB\r\n        + (systemDiskAddon \/ 50) * ADDON_PRICES.systemDiskPer50GiB\r\n        + (dataDiskAddon \/ 500) * ADDON_PRICES.dataDiskPer500GiB,\r\n      addons: { cpu: cpuAddon, memoryGiB: memoryAddon, systemDiskGiB: systemDiskAddon, dataDiskGiB: dataDiskAddon },\r\n    };\r\n  }\r\n\r\n  \/** Which resource forced this tier instead of the next smaller one. *\/\r\n  function bindingResources(tier, required, utilisation) {\r\n    const index = SERVER_TIERS.indexOf(tier);\r\n    const smaller = index > 0 ? SERVER_TIERS[index - 1] : (index === -1 ? SERVER_TIERS.at(-1) : null);\r\n    if (smaller) return TIER_KEYS.filter((key) => required[key] > smaller[key]);\r\n    \/\/ Smallest tier: nothing forced it, report what uses it up the most.\r\n    return [TIER_KEYS.map((key) => [key, utilisation[key]]).sort((left, right) => right[1] - left[1])[0][0]];\r\n  }\r\n\r\n  function calculateSizing({ users, selectedIds, dataVolumeGiB = null, model = SIZING_MODEL }) {\r\n    const selected = APP_CATALOG.filter((app) => selectedIds.has(app.id));\r\n    const calculate = model === \"measured\" ? calculateMeasuredSizing : calculateLegacySizing;\r\n    const sizing = calculate({ users, selected, dataVolumeGiB });\r\n\r\n    const tier = SERVER_TIERS.find((candidate) => TIER_KEYS.every((key) => candidate[key] >= sizing.required[key]))\r\n      ?? extendedTier(sizing.required);\r\n\r\n    \/\/ Utilisation reports the true need against the delivered size. It uses the\r\n    \/\/ unrounded requirement so a small tenant does not show four full bars.\r\n    const utilisation = Object.fromEntries(TIER_KEYS.map((key) => [key, sizing.raw[key] \/ tier[key]]));\r\n\r\n    const missing = [];\r\n    selected.forEach((app) => (app.requires ?? []).forEach((id) => {\r\n      if (!selectedIds.has(id)) missing.push({ app, requires: APP_CATALOG.find((entry) => entry.id === id) });\r\n    }));\r\n\r\n    return {\r\n      ...sizing,\r\n      users,\r\n      selected,\r\n      payloadIsAuto: dataVolumeGiB === null,\r\n      tier,\r\n      utilisation,\r\n      binding: bindingResources(tier, sizing.required, utilisation),\r\n      missing,\r\n      empty: selected.length === 0,\r\n    };\r\n  }\r\n\r\n  \/* ------------------------------------------------------------------ *\/\r\n  \/* Interface                                                           *\/\r\n  \/* ------------------------------------------------------------------ *\/\r\n\r\n  const integerFormat = new Intl.NumberFormat(\"de-DE\");\r\n  const euroFormat = new Intl.NumberFormat(\"de-DE\", { style: \"currency\", currency: \"EUR\", maximumFractionDigits: 0 });\r\n\r\n  function formatGiB(value) {\r\n    return `${(Number.isInteger(value) ? String(value) : value.toFixed(1)).replace(\".\", \",\")} GB`;\r\n  }\r\n\r\n  function formatMemory(mib) {\r\n    return mib >= 1024 ? `${(mib \/ 1024).toFixed(1).replace(\".\", \",\")} GB` : `${integerFormat.format(Math.round(mib))} MB`;\r\n  }\r\n\r\n  \/\/ The breakdown has to add up on screen, so it stays in whole megabytes\r\n  \/\/ instead of rounding every row to one decimal gigabyte.\r\n  function formatMemoryExact(mib) {\r\n    return `${integerFormat.format(Math.round(mib))} MB`;\r\n  }\r\n\r\n  function element(tag, className, text) {\r\n    const node = document.createElement(tag);\r\n    if (className) node.className = className;\r\n    if (text !== undefined) node.textContent = text;\r\n    return node;\r\n  }\r\n\r\n  function readState() {\r\n    const params = new URLSearchParams(globalThis.location?.hash.replace(\/^#\/, \"\") ?? \"\");\r\n    const apps = params.get(\"a\");\r\n    const ids = apps === null\r\n      ? DEFAULT_SELECTION\r\n      : apps.split(\",\").map((id) => id.trim()).filter((id) => APP_CATALOG.some((app) => app.id === id));\r\n    return {\r\n      users: normalizeUsers(params.get(\"u\"), USER_LIMITS.initial),\r\n      selectedIds: new Set(ids),\r\n      dataVolumeGiB: normalizeDataVolume(params.get(\"d\")),\r\n      \/\/ Internal comparison switch, deliberately not a visible control.\r\n      model: params.get(\"m\") === \"measured\" ? \"measured\" : SIZING_MODEL,\r\n    };\r\n  }\r\n\r\n  function createController(root = document, { syncPermalink = true } = {}) {\r\n    const $ = (selector) => root.querySelector(selector);\r\n    const elements = {\r\n      userInput: $(\"#user-count\"),\r\n      userRange: $(\"#user-range\"),\r\n      rangeTicks: $(\"#range-ticks\"),\r\n      dataInput: $(\"#data-volume\"),\r\n      dataAuto: $(\"#data-auto\"),\r\n      dataHint: $(\"#data-hint\"),\r\n      includedGrid: $(\"#included-grid\"),\r\n      popularGrid: $(\"#popular-grid\"),\r\n      allApps: $(\"#all-apps\"),\r\n      toggleAll: $(\"#toggle-all-apps\"),\r\n      search: $(\"#app-search\"),\r\n      categoryFilters: $(\"#category-filters\"),\r\n      appGrid: $(\"#app-grid\"),\r\n      emptyState: $(\"#empty-state\"),\r\n      selectionCount: $(\"#selection-count\"),\r\n      notices: $(\"#notices\"),\r\n      shareButton: $(\"#share-button\"),\r\n      shareStatus: $(\"#share-status\"),\r\n    };\r\n\r\n    const initial = readState();\r\n    const state = {\r\n      users: initial.users,\r\n      selectedIds: initial.selectedIds,\r\n      dataVolumeGiB: initial.dataVolumeGiB,\r\n      model: initial.model,\r\n      showAll: false,\r\n      onlySelected: false,\r\n      category: \"Alle\",\r\n      search: \"\",\r\n    };\r\n\r\n    \/* ---------- state changes ---------- *\/\r\n\r\n    function syncHash() {\r\n      const params = new URLSearchParams();\r\n      params.set(\"u\", String(state.users));\r\n      params.set(\"a\", [...state.selectedIds].join(\",\"));\r\n      if (state.dataVolumeGiB !== null) params.set(\"d\", String(state.dataVolumeGiB));\r\n      if (state.model !== SIZING_MODEL) params.set(\"m\", state.model);\r\n      const hash = `#${params.toString()}`;\r\n      if (syncPermalink && globalThis.location && globalThis.location.hash !== hash) {\r\n        globalThis.history.replaceState(null, \"\", hash);\r\n      }\r\n    }\r\n\r\n    function setUsers(value, { fromInput = false } = {}) {\r\n      state.users = normalizeUsers(value, USER_LIMITS.min);\r\n      if (!fromInput || elements.userInput.value !== String(state.users)) {\r\n        elements.userInput.value = String(state.users);\r\n      }\r\n      elements.userRange.value = String(state.users);\r\n      const progress = ((state.users - USER_LIMITS.min) \/ (USER_LIMITS.max - USER_LIMITS.min)) * 100;\r\n      const thumbCorrection = 12 - progress * 0.24;\r\n      elements.userRange.style.setProperty(\"--range-progress\", `calc(${progress}% + ${thumbCorrection}px)`);\r\n      renderData();\r\n      renderResult();\r\n      syncHash();\r\n    }\r\n\r\n    function toggleApp(id) {\r\n      if (state.selectedIds.has(id)) state.selectedIds.delete(id); else state.selectedIds.add(id);\r\n      renderApps();\r\n      renderData();\r\n      renderResult();\r\n      syncHash();\r\n    }\r\n\r\n    function setSelection(ids) {\r\n      state.selectedIds = new Set(ids);\r\n      state.onlySelected = false;\r\n      state.category = \"Alle\";\r\n      state.search = \"\";\r\n      elements.search.value = \"\";\r\n      renderCategories();\r\n      renderApps();\r\n      renderData();\r\n      renderResult();\r\n      syncHash();\r\n    }\r\n\r\n    function setDataVolume(value) {\r\n      state.dataVolumeGiB = normalizeDataVolume(value);\r\n      renderData();\r\n      renderResult();\r\n      syncHash();\r\n    }\r\n\r\n    \/* ---------- rendering ---------- *\/\r\n\r\n    function renderTicks() {\r\n      [1, 100, 200, 300, 400, 500].forEach((value) => {\r\n        const tick = element(\"span\", null, String(value));\r\n        const progress = ((value - USER_LIMITS.min) \/ (USER_LIMITS.max - USER_LIMITS.min)) * 100;\r\n        const thumbCorrection = 12 - progress * 0.24;\r\n        tick.style.left = `calc(${progress}% + ${thumbCorrection}px)`;\r\n        elements.rangeTicks.append(tick);\r\n      });\r\n    }\r\n\r\n    function renderData() {\r\n      const auto = Math.round(calculateSizing({ ...state, dataVolumeGiB: null }).payloadGiB);\r\n      elements.dataAuto.checked = state.dataVolumeGiB === null;\r\n      elements.dataInput.disabled = state.dataVolumeGiB === null;\r\n      elements.dataInput.value = String(state.dataVolumeGiB === null ? auto : state.dataVolumeGiB);\r\n      elements.dataHint.textContent = state.dataVolumeGiB === null\r\n        ? `Automatisch aus Nutzerzahl und Apps gesch\u00e4tzt: ${integerFormat.format(auto)} GB. F\u00fcr ein belastbares Angebot den echten Wert eintragen.`\r\n        : `Fester Wert. Zur\u00fcck zur Sch\u00e4tzung (${integerFormat.format(auto)} GB) \u00fcber \u201eautomatisch\".`;\r\n    }\r\n\r\n    function cardFooter(app) {\r\n      const foot = element(\"div\", \"app-foot\");\r\n      foot.append(element(\"small\", null, app.category));\r\n\r\n      if (state.model === \"legacy\") {\r\n        \/\/ Primary line: the value the recommendation is calculated from.\r\n        \/\/ Second line: what the app really uses, for comparison.\r\n        const legacy = legacyResourcesForApp(app);\r\n        const metric = element(\"span\", `app-metric${legacy.fromWorkbook ? \"\" : \" app-metric--assumed\"}`);\r\n        metric.textContent = legacy.fromWorkbook\r\n          ? `${formatMemory(legacy.memoryMiB)} RAM je 10 Nutzer`\r\n          : `${formatMemory(legacy.memoryMiB)} RAM je 10 Nutzer \u00b7 Klassenwert`;\r\n        metric.title = legacy.fromWorkbook\r\n          ? \"Wert aus dem GMZ VM-Kalkulator\"\r\n          : `Im VM-Kalkulator nicht enthalten, gerechnet mit dem Wert der Gr\u00f6\u00dfenklasse \u201e${PROFILE_LABELS[app.profile]}\"`;\r\n        foot.append(metric);\r\n        const note = element(\"span\", \"app-metric app-metric--soft\");\r\n        note.textContent = app.benchmark\r\n          ? `gemessen: ${formatMemory(app.benchmark.memoryMiB)}`\r\n          : \"nicht vermessen\";\r\n        note.title = app.benchmark ? SOURCE_LABELS[app.source] : \"F\u00fcr diese App liegt keine Messung vor\";\r\n        foot.append(note);\r\n      } else {\r\n        const resource = resourcesForApp(app, state.users);\r\n        const measured = [\"lab\", \"prod\", \"mixed\"].includes(app.source);\r\n        const metric = element(\"span\", `app-metric${measured ? \"\" : \" app-metric--assumed\"}`);\r\n        metric.textContent = `${formatMemory(resource.baseMemoryMiB)} RAM \u00b7 ${measured ? \"gemessen\" : \"gesch\u00e4tzt\"}`;\r\n        metric.title = `${SOURCE_LABELS[app.source]} \u00b7 Lastprofil ${PROFILE_LABELS[app.profile]}`;\r\n        foot.append(metric);\r\n      }\r\n\r\n      if (app.supersededBy) {\r\n        const successor = APP_CATALOG.find((entry) => entry.id === app.supersededBy);\r\n        foot.append(element(\"span\", \"app-flag\", `abgel\u00f6st durch ${successor.name}`));\r\n      }\r\n      return foot;\r\n    }\r\n\r\n    function renderIncluded() {\r\n      const card = element(\"div\", \"app-card app-card--locked\");\r\n      card.append(element(\"span\", \"app-check app-check--locked\", \"\u2713\"));\r\n      card.append(element(\"strong\", null, \"Authentik\"));\r\n      card.append(element(\"span\", \"app-description\", \"Single Sign-on f\u00fcr alle Apps - in jedem Tenant enthalten.\"));\r\n      const foot = element(\"div\", \"app-foot\");\r\n      foot.append(element(\"small\", null, \"Immer dabei\"));\r\n      foot.append(element(\"span\", \"app-metric\", state.model === \"legacy\"\r\n        ? `${formatMemory(LEGACY_AUTHENTIK.memoryMiB)} RAM je 10 Nutzer`\r\n        : `${formatMemory(PLATFORM_BASELINE.authentik.memoryMiB)} RAM \u00b7 gemessen`));\r\n      card.append(foot);\r\n      elements.includedGrid.replaceChildren(card);\r\n    }\r\n\r\n    function appCard(app) {\r\n      const active = state.selectedIds.has(app.id);\r\n      const button = element(\"button\", `app-card${active ? \" selected\" : \"\"}`);\r\n      button.type = \"button\";\r\n      button.dataset.appId = app.id;\r\n      button.setAttribute(\"aria-pressed\", String(active));\r\n      button.append(element(\"span\", \"app-check\", active ? \"\u2713\" : \"+\"));\r\n      button.append(element(\"strong\", null, app.name));\r\n      button.append(element(\"span\", \"app-description\", app.description));\r\n      button.append(cardFooter(app));\r\n      button.addEventListener(\"click\", () => toggleApp(app.id));\r\n      return button;\r\n    }\r\n\r\n    function renderApps() {\r\n      \/\/ Popular tiles plus anything the user picked that is not among them, so\r\n      \/\/ the current selection is never hidden behind the \"show all\" toggle.\r\n      const popular = POPULAR_ORDER.map((id) => APP_CATALOG.find((app) => app.id === id));\r\n      const extra = APP_CATALOG.filter((app) => !app.popular && state.selectedIds.has(app.id));\r\n      elements.popularGrid.replaceChildren(...[...popular, ...extra].map(appCard));\r\n\r\n      const query = state.search.toLocaleLowerCase(\"de\");\r\n      const visible = orderAppsBySelection(\r\n        APP_CATALOG.filter((app) =>\r\n          (!state.onlySelected || state.selectedIds.has(app.id)) &&\r\n          (state.category === \"Alle\" || app.category === state.category) &&\r\n          (!query || `${app.name} ${app.id} ${app.description}`.toLocaleLowerCase(\"de\").includes(query))\r\n        ),\r\n        state.selectedIds,\r\n      );\r\n      elements.appGrid.replaceChildren(...visible.map(appCard));\r\n      elements.emptyState.hidden = visible.length > 0;\r\n\r\n      elements.selectionCount.textContent = state.selectedIds.size === 1\r\n        ? \"1 App ausgew\u00e4hlt\"\r\n        : `${state.selectedIds.size} Apps ausgew\u00e4hlt`;\r\n      elements.allApps.hidden = !state.showAll;\r\n      elements.toggleAll.setAttribute(\"aria-expanded\", String(state.showAll));\r\n      elements.toggleAll.textContent = state.showAll\r\n        ? \"Weniger Apps anzeigen\"\r\n        : `Alle ${APP_CATALOG.length} Apps anzeigen`;\r\n    }\r\n\r\n    function renderCategories() {\r\n      const categories = [\"Alle\", ...new Set(APP_CATALOG.map((app) => app.category))].sort((left, right) =>\r\n        left === \"Alle\" ? -1 : right === \"Alle\" ? 1 : left.localeCompare(right, \"de\"));\r\n      const buttons = categories.map((category) => {\r\n        const active = !state.onlySelected && state.category === category;\r\n        const button = element(\"button\", active ? \"active\" : null, category);\r\n        button.type = \"button\";\r\n        button.setAttribute(\"aria-pressed\", String(active));\r\n        button.addEventListener(\"click\", () => {\r\n          state.category = category;\r\n          state.onlySelected = false;\r\n          renderCategories();\r\n          renderApps();\r\n        });\r\n        return button;\r\n      });\r\n      const mine = element(\"button\", `filter-mine${state.onlySelected ? \" active\" : \"\"}`, \"Meine Auswahl\");\r\n      mine.type = \"button\";\r\n      mine.setAttribute(\"aria-pressed\", String(state.onlySelected));\r\n      mine.addEventListener(\"click\", () => {\r\n        state.onlySelected = !state.onlySelected;\r\n        if (state.onlySelected) state.category = \"Alle\";\r\n        renderCategories();\r\n        renderApps();\r\n      });\r\n      elements.categoryFilters.replaceChildren(mine, ...buttons);\r\n    }\r\n\r\n    function renderNotices(result) {\r\n      const notices = [];\r\n      result.missing.forEach(({ app, requires }) => {\r\n        const notice = element(\"p\", \"notice\");\r\n        notice.append(document.createTextNode(`${app.name} braucht ${requires.name} als Unterbau. `));\r\n        const add = element(\"button\", \"notice-action\", `${requires.name} hinzuf\u00fcgen`);\r\n        add.type = \"button\";\r\n        add.addEventListener(\"click\", () => toggleApp(requires.id));\r\n        notice.append(add);\r\n        notices.push(notice);\r\n      });\r\n      if (state.selectedIds.has(\"collabora\") && state.selectedIds.has(\"eurooffice\")) {\r\n        notices.push(element(\"p\", \"notice\", \"Collabora und Euro-Office sind Alternativen f\u00fcr dieselbe Aufgabe. Normalerweise wird nur eines der beiden betrieben.\"));\r\n      }\r\n      if (state.selectedIds.has(\"stirling-pdf\")) {\r\n        notices.push(element(\"p\", \"notice\", \"Stirling PDF ist im Katalog durch BentoPDF abgel\u00f6st. BentoPDF arbeitet im Browser und belegt im Betrieb rund 1 GB weniger Arbeitsspeicher.\"));\r\n      }\r\n      elements.notices.replaceChildren(...notices);\r\n    }\r\n\r\n    function renderBreakdown(result) {\r\n      const table = element(\"table\", \"breakdown\");\r\n      const body = element(\"tbody\");\r\n      result.memoryBreakdown.forEach((row) => {\r\n        const tr = element(\"tr\", `breakdown-${row.kind}`);\r\n        tr.append(element(\"th\", null, row.label));\r\n        tr.append(element(\"td\", null, formatMemoryExact(row.value)));\r\n        body.append(tr);\r\n      });\r\n      const sum = element(\"tr\", \"breakdown-sum\");\r\n      sum.append(element(\"th\", null, \"Summe\"));\r\n      sum.append(element(\"td\", null, `${formatMemoryExact(result.memoryTotalMiB)} (${formatMemory(result.memoryTotalMiB)})`));\r\n      body.append(sum);\r\n      table.append(body);\r\n      return table;\r\n    }\r\n\r\n    function roundingText(result) {\r\n      return result.model === \"legacy\"\r\n        ? `Der VM-Kalkulator rundet den Arbeitsspeicher auf halbe Gigabyte auf: ${formatGiB(result.required.memoryGiB)} ben\u00f6tigt. Eine prozentuale Betriebsreserve kennt er nicht; sie steckt im Grundbedarf pro VM.`\r\n        : \"Die Betriebsreserve wird nur auf die Apps gerechnet, nicht noch einmal auf die bereits gemessene Plattform-Grundlast.\";\r\n    }\r\n\r\n    function confidenceText(result) {\r\n      if (result.model === \"measured\") {\r\n        const date = new Intl.DateTimeFormat(\"de-DE\").format(new Date(BENCHMARK_META.measuredAt));\r\n        return `Vergleichsansicht auf Basis von Messwerten (Stand ${date}), nicht die freigegebene Kalkulationsgrundlage.`;\r\n      }\r\n      const base = `Gerechnet wird mit den ${MODEL_LABELS.legacy}; Backend-Dienste z\u00e4hlen je Typ einmal.`;\r\n      if (result.empty) return `${base} Noch keine App gew\u00e4hlt.`;\r\n      const outside = result.selected.length - result.workbookCount;\r\n      return outside === 0\r\n        ? `${base} Alle ${result.selected.length} gew\u00e4hlten Apps stehen dort mit eigenen Werten.`\r\n        : `${base} ${outside} der ${result.selected.length} gew\u00e4hlten Apps kannte er noch nicht; sie rechnen mit dem Wert ihrer Gr\u00f6\u00dfenklasse.`;\r\n    }\r\n\r\n    function renderResult() {\r\n      const result = calculateSizing(state);\r\n      const write = (selector, value) => { root.querySelector(selector).textContent = value; };\r\n      renderNotices(result);\r\n\r\n      const tier = result.tier;\r\n      write(\"#tier-name\", tier.name);\r\n      write(\"#tier-label\", tier.label);\r\n      write(\"#tier-summary\", result.empty\r\n        ? `nur Grundbedarf f\u00fcr ${integerFormat.format(state.users)} Nutzer`\r\n        : `f\u00fcr ${integerFormat.format(state.users)} Nutzer und ${result.selected.length} ${result.selected.length === 1 ? \"App\" : \"Apps\"}`);\r\n      write(\"#result-cpu\", String(tier.cpu));\r\n      write(\"#result-memory\", formatGiB(tier.memoryGiB));\r\n      write(\"#result-system-disk\", `${integerFormat.format(tier.systemDiskGiB)} GB`);\r\n      write(\"#result-data-disk\", `${integerFormat.format(tier.dataDiskGiB)} GB`);\r\n      write(\"#result-price\", euroFormat.format(tier.monthlyEuro));\r\n\r\n      const bars = root.querySelector(\"#utilisation\");\r\n      bars.replaceChildren(...Object.entries(RESOURCE_LABELS).map(([key, label]) => {\r\n        const share = Math.min(1, result.utilisation[key]);\r\n        const row = element(\"div\", `bar${result.binding.includes(key) ? \" bar--binding\" : \"\"}`);\r\n        row.append(element(\"span\", \"bar-label\", label));\r\n        const track = element(\"span\", \"bar-track\");\r\n        const fill = element(\"span\", \"bar-fill\");\r\n        fill.style.width = `${(share * 100).toFixed(1)}%`;\r\n        track.append(fill);\r\n        row.append(track);\r\n        row.append(element(\"span\", \"bar-value\", `${Math.round(result.utilisation[key] * 100)} %`));\r\n        return row;\r\n      }));\r\n\r\n      const bindingLabels = result.binding.map((key) => RESOURCE_LABELS[key]);\r\n      write(\"#binding-note\", SERVER_TIERS.indexOf(tier) === 0\r\n        ? `Am st\u00e4rksten ausgelastet: ${bindingLabels.join(\" und \")}. Kleiner als ${tier.name} gibt es nicht.`\r\n        : `Ausschlaggebend: ${bindingLabels.join(\" und \")} - daf\u00fcr reicht die n\u00e4chstkleinere Gr\u00f6\u00dfe nicht.`);\r\n\r\n      root.querySelector(\"#memory-breakdown\").replaceChildren(renderBreakdown(result));\r\n      write(\"#rounding-note\", roundingText(result));\r\n\r\n      const addons = tier.addons;\r\n      const addonNote = root.querySelector(\"#addon-note\");\r\n      addonNote.hidden = !addons;\r\n      if (addons) {\r\n        const parts = [];\r\n        if (addons.cpu) parts.push(`+${addons.cpu} vCPU`);\r\n        if (addons.memoryGiB) parts.push(`+${formatGiB(addons.memoryGiB)} RAM`);\r\n        if (addons.systemDiskGiB) parts.push(`+${addons.systemDiskGiB} GB SSD`);\r\n        if (addons.dataDiskGiB) parts.push(`+${integerFormat.format(addons.dataDiskGiB)} GB HDD`);\r\n        addonNote.textContent = `\u00dcber XL hinaus: ${parts.join(\", \")} als Add-on zum XL-Grundpreis.`;\r\n      }\r\n\r\n      write(\"#confidence-note\", confidenceText(result));\r\n      write(\"#contract-note\", CONTRACT_TERMS.text);\r\n      return result;\r\n    }\r\n\r\n    function summaryText() {\r\n      const result = calculateSizing(state);\r\n      return [\r\n        `GMZ Cloud - Serverempfehlung ${result.tier.name} (${result.tier.label})`,\r\n        `${result.tier.cpu} vCPU \u00b7 ${formatGiB(result.tier.memoryGiB)} RAM \u00b7 ${result.tier.systemDiskGiB} GB SSD \u00b7 ${result.tier.dataDiskGiB} GB HDD`,\r\n        `${euroFormat.format(result.tier.monthlyEuro)} pro Monat`,\r\n        \"\",\r\n        `Nutzer: ${state.users}`,\r\n        `Nutzdaten: ${integerFormat.format(Math.round(result.payloadGiB))} GB${result.payloadIsAuto ? \" (gesch\u00e4tzt)\" : \"\"}`,\r\n        `Apps: Authentik (immer enthalten)${result.selected.length ? \", \" + result.selected.map((app) => app.name).join(\", \") : \"\"}`,\r\n        result.databaseLabel ? `Backend-Dienste: ${result.databaseLabel}` : \"\",\r\n        `Grundlage: Kalkulation mit ${MODEL_LABELS[result.model]}`,\r\n        \"\",\r\n        CONTRACT_TERMS.text,\r\n        syncPermalink && globalThis.location ? globalThis.location.href : \"\",\r\n      ].filter(Boolean).join(\"\\n\");\r\n    }\r\n\r\n    \/* ---------- events ---------- *\/\r\n\r\n    elements.userRange.addEventListener(\"input\", (event) => setUsers(event.target.value));\r\n    elements.userInput.addEventListener(\"input\", (event) => setUsers(event.target.value, { fromInput: true }));\r\n    elements.userInput.addEventListener(\"blur\", () => setUsers(elements.userInput.value));\r\n    elements.dataInput.addEventListener(\"input\", (event) => setDataVolume(event.target.value));\r\n    elements.dataInput.addEventListener(\"blur\", () => setDataVolume(elements.dataInput.value));\r\n    elements.dataAuto.addEventListener(\"change\", (event) => setDataVolume(event.target.checked ? null : elements.dataInput.value));\r\n    elements.search.addEventListener(\"input\", (event) => {\r\n      state.search = event.target.value.trim();\r\n      state.onlySelected = false;\r\n      renderCategories();\r\n      renderApps();\r\n    });\r\n    elements.toggleAll.addEventListener(\"click\", () => {\r\n      state.showAll = !state.showAll;\r\n      renderApps();\r\n      if (state.showAll) elements.search.focus();\r\n    });\r\n    root.querySelectorAll(\"[data-preset]\").forEach((button) => {\r\n      button.addEventListener(\"click\", () => {\r\n        const preset = button.dataset.preset;\r\n        if (preset === \"all\") setSelection(APP_CATALOG.map((app) => app.id));\r\n        else if (preset === \"none\") setSelection([]);\r\n        else setSelection(PRESETS[preset].apps);\r\n      });\r\n    });\r\n    elements.shareButton?.addEventListener(\"click\", async () => {\r\n      try {\r\n        await navigator.clipboard.writeText(summaryText());\r\n        elements.shareStatus.textContent = \"Zusammenfassung und Link kopiert.\";\r\n      } catch {\r\n        elements.shareStatus.textContent = \"Kopieren nicht m\u00f6glich - bitte den Link aus der Adresszeile verwenden.\";\r\n      }\r\n    });\r\n\r\n    \/\/ Pasting a shared link into an already open tab must reconfigure the page.\r\n    \/\/ replaceState from syncHash does not fire this event, so there is no loop.\r\n    if (syncPermalink) globalThis.addEventListener?.(\"hashchange\", () => {\r\n      const next = readState();\r\n      state.users = next.users;\r\n      state.selectedIds = next.selectedIds;\r\n      state.dataVolumeGiB = next.dataVolumeGiB;\r\n      state.model = next.model;\r\n      renderIncluded();\r\n      renderCategories();\r\n      renderApps();\r\n      setUsers(state.users);\r\n    });\r\n\r\n    renderTicks();\r\n    renderIncluded();\r\n    renderCategories();\r\n    renderApps();\r\n    setUsers(state.users);\r\n  }\r\n\r\n  if (typeof document !== \"undefined\" && document.querySelector(\"#user-count\")) createController();\r\n\r\n  \/\/ GMZ_MARKUP is generated at build time from index.html, not user input.\r\n  const previous = document.currentScript?.previousElementSibling;\r\n  const mount = previous?.hasAttribute?.(\"data-gmz-rechner\") && !previous.dataset.gmzMounted\r\n    ? previous\r\n    : document.querySelector(\"[data-gmz-rechner]:not([data-gmz-mounted])\");\r\n  mount.dataset.gmzMounted = \"1\";\r\n  const shadow = mount.attachShadow({ mode: \"open\" });\r\n  const style = document.createElement(\"style\");\r\n  style.textContent = GMZ_STYLES;\r\n  const wrap = document.createElement(\"div\");\r\n  wrap.className = \"gmz-root\";\r\n  wrap.innerHTML = GMZ_MARKUP;\r\n  shadow.append(style, wrap);\r\n  const wantsPermalink = mount.dataset.permalink !== \"off\" && !globalThis.__gmzRechnerPermalink;\r\n  if (wantsPermalink) globalThis.__gmzRechnerPermalink = true;\r\n  createController(shadow, { syncPermalink: wantsPermalink });\r\n})();\r\n<\/script>\r\n<\/div>\n                <\/div>\n              <\/div>\n            <\/div>\n          <\/div>\n        <\/div>\n      <\/div>\n    <\/div>\n  <\/div>\n  <div data-colibri-component=\"section\" data-colibri-id=\"11649-c14\" id=\"reusable-sections\" class=\"h-section h-section-global-spacing d-flex align-items-lg-center align-items-md-center align-items-center style-11407 style-local-11649-c14 position-relative\">\n    <!---->\n    <!---->\n    <div class=\"h-section-grid-container h-section-boxed-container\">\n      <!---->\n      <div data-colibri-id=\"11649-c15\" class=\"h-row-container gutters-row-lg-2 gutters-row-md-2 gutters-row-0 gutters-row-v-lg-2 gutters-row-v-md-2 gutters-row-v-2 style-11408 style-local-11649-c15 position-relative\">\n        <!---->\n        <div class=\"h-row justify-content-lg-center justify-content-md-center justify-content-center align-items-lg-stretch align-items-md-stretch align-items-stretch gutters-col-lg-2 gutters-col-md-2 gutters-col-0 gutters-col-v-lg-2 gutters-col-v-md-2 gutters-col-v-2\">\n          <!---->\n          <div class=\"h-column h-column-container d-flex h-col-lg-auto h-col-md-auto h-col-auto style-11409-outer style-local-11649-c16-outer\">\n            <div data-colibri-id=\"11649-c16\" class=\"d-flex h-flex-basis h-column__inner h-px-lg-2 h-px-md-2 h-px-2 v-inner-lg-2 v-inner-md-2 v-inner-2 style-11409 style-local-11649-c16 position-relative\">\n              <!---->\n              <!---->\n              <div class=\"w-100 h-y-container h-column__content h-column__v-align flex-basis-100 align-self-lg-start align-self-md-start align-self-start\">\n                <!---->\n                <div data-colibri-id=\"11649-c17\" class=\"h-text h-text-component style-11410 style-local-11649-c17 position-relative h-element\">\n                  <!---->\n                  <!---->\n                  <div class=\"\">\n                    <h5><span style=\"color: rgb(0, 0, 0); font-weight: 400;\">Sie haben die passende Konfiguration gefunden. <br>Hier ein paar Beispiele, welche Anwendungen darauf laufen k\u00f6nnen.<\/span>\n                      <br>\n                    <\/h5>\n                    <p>\n                      <br>\n                    <\/p>\n                  <\/div>\n                <\/div>\n              <\/div>\n            <\/div>\n          <\/div>\n        <\/div>\n      <\/div>\n      <div data-colibri-id=\"11649-c18\" class=\"h-row-container gutters-row-lg-2 gutters-row-md-0 gutters-row-2 gutters-row-v-lg-2 gutters-row-v-md-0 gutters-row-v-2 style-11411 style-local-11649-c18 position-relative\">\n        <!---->\n        <div class=\"h-row justify-content-lg-center justify-content-md-center justify-content-center align-items-lg-stretch align-items-md-stretch align-items-stretch gutters-col-lg-2 gutters-col-md-0 gutters-col-2 gutters-col-v-lg-2 gutters-col-v-md-0 gutters-col-v-2\">\n          <!---->\n          <div class=\"h-column h-column-container d-flex h-col-lg-3 h-col-md-3 h-col-12 style-11412-outer style-local-11649-c19-outer\">\n            <div data-colibri-id=\"11649-c19\" class=\"d-flex h-flex-basis h-column__inner h-px-lg-2 h-px-md-3 h-px-2 v-inner-lg-0 v-inner-md-3 v-inner-2 style-11412 style-local-11649-c19 position-relative\">\n              <!---->\n              <!---->\n              <div class=\"w-100 h-y-container h-column__content h-column__v-align flex-basis-100 align-self-lg-start align-self-md-start align-self-start\">\n                <!---->\n                <div data-colibri-id=\"11649-c20\" class=\"h-icon style-11413 style-local-11649-c20 position-relative h-element\">\n                  <!----><span class=\"h-svg-icon h-icon__icon style-11413-icon style-local-11649-c20-icon\"><!--Icon by Font Awesome (https:\/\/fontawesome.com)-->\n    <svg version=\"1.1\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" xmlns:xlink=\"http:\/\/www.w3.org\/1999\/xlink\" id=\"warning\" viewBox=\"0 0 1792 1896.0833\">\n      <path d=\"M1024 1375v-190q0-14-9.5-23.5T992 1152H800q-13 0-22.5 9.5T768 1185v190q0 14 9.5 23.5t22.5 9.5h192q13 0 22.5-9.5t9.5-23.5zm-2-374l18-459q0-12-10-19-13-11-24-11H786q-11 0-24 11-10 7-10 21l17 457q0 10 10 16.5t24 6.5h185q14 0 23.5-6.5t10.5-16.5zm-14-934l768 1408q35 63-2 126-17 29-46.5 46t-63.5 17H128q-34 0-63.5-17T18 1601q-37-63-2-126L784 67q17-31 47-49t65-18 65 18 47 49z\"><\/path>\n    <\/svg><\/span><\/div>\n                <div data-colibri-id=\"11649-c21\" class=\"h-global-transition-all h-heading style-11414 style-local-11649-c21 position-relative h-element\">\n                  <!---->\n                  <div class=\"h-heading__outer style-11414 style-local-11649-c21\">\n                    <!---->\n                    <!---->\n                    <h5 class=\"\"><span style=\"color: rgb(6, 80, 155);\">Authentik<\/span><\/h5>\n                  <\/div>\n                <\/div>\n                <div data-colibri-id=\"11649-c22\" class=\"h-text h-text-component style-11415 style-local-11649-c22 position-relative h-element\">\n                  <!---->\n                  <!---->\n                  <div class=\"\">\n                    <p><span style=\"color: rgb(0, 0, 0); font-weight: 400;\">Ein Login f\u00fcr alle Cloud Business Apps<\/span><\/p>\n                    <p><span style=\"color: rgb(0, 0, 0);\">Mit Authentik reicht ein einziger Login f\u00fcr den Zugriff auf alle Cloud Business Apps. Kein Passwort-Chaos, keine mehrfachen Anmeldungen. Zwei-Faktor-Authentifizierung und feine Rechteverwaltung sorgen zus\u00e4tzlich f\u00fcr Sicherheit. <\/span><\/p>\n                  <\/div>\n                <\/div>\n              <\/div>\n            <\/div>\n          <\/div>\n          <div class=\"h-column h-column-container d-flex h-col-lg-3 h-col-md-3 h-col-12 style-11412-outer style-local-11649-c23-outer\">\n            <div data-colibri-id=\"11649-c23\" class=\"d-flex h-flex-basis h-column__inner h-px-lg-2 h-px-md-3 h-px-2 v-inner-lg-0 v-inner-md-3 v-inner-2 style-11412 style-local-11649-c23 position-relative\">\n              <!---->\n              <!---->\n              <div class=\"w-100 h-y-container h-column__content h-column__v-align flex-basis-100 align-self-lg-start align-self-md-start align-self-start\">\n                <!---->\n                <div data-colibri-id=\"11649-c24\" class=\"h-icon style-11413 style-local-11649-c24 position-relative h-element\">\n                  <!----><span class=\"h-svg-icon h-icon__icon style-11413-icon style-local-11649-c24-icon\"><!--Icon by Font Awesome (https:\/\/fontawesome.com)-->\n    <svg version=\"1.1\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" xmlns:xlink=\"http:\/\/www.w3.org\/1999\/xlink\" id=\"cloud\" viewBox=\"0 0 1920 1896.0833\">\n      <path d=\"M1920 1152q0 159-112.5 271.5T1536 1536H448q-185 0-316.5-131.5T0 1088q0-132 71-241.5T258 683q-2-28-2-43 0-212 150-362t362-150q158 0 286.5 88T1242 446q70-62 166-62 106 0 181 75t75 181q0 75-41 138 129 30 213 134.5t84 239.5z\"><\/path>\n    <\/svg><\/span><\/div>\n                <div data-colibri-id=\"11649-c25\" class=\"h-global-transition-all h-heading style-11414 style-local-11649-c25 position-relative h-element\">\n                  <!---->\n                  <div class=\"h-heading__outer style-11414 style-local-11649-c25\">\n                    <!---->\n                    <!---->\n                    <h5 class=\"\"><span style=\"color: rgb(6, 80, 155);\">Nextcloud<\/span><\/h5>\n                  <\/div>\n                <\/div>\n                <div data-colibri-id=\"11649-c26\" class=\"h-text h-text-component style-11415 style-local-11649-c26 position-relative h-element\">\n                  <!---->\n                  <!---->\n                  <div class=\"\">\n                    <p><span style=\"font-weight: 400; color: rgb(0, 0, 0);\">Die eigene Cloud f\u00fcr Dateien und Zusammenarbeit.<\/span><\/p>\n                    <p><span style=\"color: rgb(0, 0, 0);\">Nextcloud bietet eine sichere Plattform zum Speichern, Teilen und gemeinsamen Bearbeiten von Dateien, ganz ohne externe Cloud-Anbieter. Kalender, Kontakte und Videokonferenzen lassen sich direkt integrieren. <\/span><\/p>\n                  <\/div>\n                <\/div>\n              <\/div>\n            <\/div>\n          <\/div>\n          <div class=\"h-column h-column-container d-flex h-col-lg-3 h-col-md-3 h-col-12 style-11412-outer style-local-11649-c27-outer\">\n            <div data-colibri-id=\"11649-c27\" class=\"d-flex h-flex-basis h-column__inner h-px-lg-2 h-px-md-3 h-px-2 v-inner-lg-0 v-inner-md-3 v-inner-2 style-11412 style-local-11649-c27 position-relative\">\n              <!---->\n              <!---->\n              <div class=\"w-100 h-y-container h-column__content h-column__v-align flex-basis-100 align-self-lg-start align-self-md-start align-self-start\">\n                <!---->\n                <div data-colibri-id=\"11649-c28\" class=\"h-icon style-11413 style-local-11649-c28 position-relative h-element\">\n                  <!----><span class=\"h-svg-icon h-icon__icon style-11413-icon style-local-11649-c28-icon\"><!--Icon by Font Awesome (https:\/\/fontawesome.com)-->\n    <svg version=\"1.1\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" xmlns:xlink=\"http:\/\/www.w3.org\/1999\/xlink\" id=\"key\" viewBox=\"0 0 1792 1896.0833\">\n      <path d=\"M832 512q0-80-56-136t-136-56-136 56-56 136q0 42 19 83-41-19-83-19-80 0-136 56t-56 136 56 136 136 56 136-56 56-136q0-42-19-83 41 19 83 19 80 0 136-56t56-136zm851 704q0 17-49 66t-66 49q-9 0-28.5-16t-36.5-33-38.5-40-24.5-26l-96 96 220 220q28 28 28 68 0 42-39 81t-81 39q-40 0-68-28l-671-671q-176 131-365 131-163 0-265.5-102.5T0 784q0-160 95-313t248-248 313-95q163 0 265.5 102.5T1024 496q0 189-131 365l355 355 96-96q-3-3-26-24.5t-40-38.5-33-36.5-16-28.5q0-17 49-66t66-49q13 0 23 10 6 6 46 44.5t82 79.5 86.5 86 73 78 28.5 41z\"><\/path>\n    <\/svg><\/span><\/div>\n                <div data-colibri-id=\"11649-c29\" class=\"h-global-transition-all h-heading style-11414 style-local-11649-c29 position-relative h-element\">\n                  <!---->\n                  <div class=\"h-heading__outer style-11414 style-local-11649-c29\">\n                    <!---->\n                    <!---->\n                    <h5 class=\"\"><span style=\"color: rgb(6, 80, 155);\">Vaultwarden<\/span><\/h5>\n                  <\/div>\n                <\/div>\n                <div data-colibri-id=\"11649-c30\" class=\"h-text h-text-component style-11415 style-local-11649-c30 position-relative h-element\">\n                  <!---->\n                  <!---->\n                  <div class=\"\">\n                    <p><strong>Passw\u00f6rter sicher verwalten<\/strong><\/p>\n                    <p><span style=\"color: rgb(0, 0, 0);\">Vaultwarden speichert Passw\u00f6rter und Zugangsdaten zentral und verschl\u00fcsselt, statt sie verstreut in Notizen oder Browsern zu lagern. Teams k\u00f6nnen Zug\u00e4nge sicher teilen, ohne sie im Klartext weiterzugeben.<\/span><\/p>\n                  <\/div>\n                <\/div>\n              <\/div>\n            <\/div>\n          <\/div>\n          <div class=\"h-column h-column-container d-flex h-col-lg-3 h-col-md-3 h-col-12 style-11412-outer style-local-11649-c31-outer\">\n            <div data-colibri-id=\"11649-c31\" class=\"d-flex h-flex-basis h-column__inner h-px-lg-2 h-px-md-3 h-px-2 v-inner-lg-0 v-inner-md-3 v-inner-2 style-11412 style-local-11649-c31 position-relative\">\n              <!---->\n              <!---->\n              <div class=\"w-100 h-y-container h-column__content h-column__v-align flex-basis-100 align-self-lg-start align-self-md-start align-self-start\">\n                <!---->\n                <div data-colibri-id=\"11649-c32\" class=\"h-icon style-11413 style-local-11649-c32 position-relative h-element\">\n                  <!----><span class=\"h-svg-icon h-icon__icon style-11413-icon style-local-11649-c32-icon\"><!--Icon by Ionicons (https:\/\/ionicons.com)--><svg version=\"1.1\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" xmlns:xlink=\"http:\/\/www.w3.org\/1999\/xlink\" id=\"ios-paper\" viewBox=\"0 0 384 512\"><path d=\"M48 64h336v353c0 17-14 31-31 31H31c-17 0-31-14-31-31V112h32v288h16V64zm48 48v16h128v-16H96zm0 160v16h192v-16H96zm240 96v-16H96v16h240zm0-160v-16H96v16h240z\"><\/path><\/svg><\/span><\/div>\n                <div\n                  data-colibri-id=\"11649-c33\" class=\"h-global-transition-all h-heading style-11414 style-local-11649-c33 position-relative h-element\">\n                  <!---->\n                  <div class=\"h-heading__outer style-11414 style-local-11649-c33\">\n                    <!---->\n                    <!---->\n                    <h5 class=\"\"><span style=\"color: rgb(6, 80, 155);\">Paperless-ngx<\/span><\/h5>\n                  <\/div>\n              <\/div>\n              <div data-colibri-id=\"11649-c34\" class=\"h-text h-text-component style-11415 style-local-11649-c34 position-relative h-element\">\n                <!---->\n                <!---->\n                <div class=\"\">\n                  <p><strong style=\"color: rgb(0, 0, 0);\">Digitale Dokumentenverwaltung<\/strong><\/p>\n                  <p><span style=\"color: rgb(0, 0, 0);\">Paperless-ngx archiviert Dokumente digital, durchsuchbar und ordentlich sortiert, statt in Aktenordnern. Rechnungen, Vertr\u00e4ge und Belege werden automatisch erkannt und verschlagwortet.<\/span><span style=\"color: rgb(0, 0, 0);\"> <\/span><\/p>\n                <\/div>\n              <\/div>\n            <\/div>\n          <\/div>\n        <\/div>\n        <div class=\"h-column h-column-container d-flex h-col-lg-3 h-col-md-3 h-col-12 style-11412-outer style-local-11649-c35-outer\">\n          <div data-colibri-id=\"11649-c35\" class=\"d-flex h-flex-basis h-column__inner h-px-lg-2 h-px-md-3 h-px-2 v-inner-lg-0 v-inner-md-3 v-inner-2 style-11412 style-local-11649-c35 position-relative\">\n            <!---->\n            <!---->\n            <div class=\"w-100 h-y-container h-column__content h-column__v-align flex-basis-100 align-self-lg-start align-self-md-start align-self-start\">\n              <!---->\n              <div data-colibri-id=\"11649-c36\" class=\"h-icon style-11413 style-local-11649-c36 position-relative h-element\">\n                <!----><span class=\"h-svg-icon h-icon__icon style-11413-icon style-local-11649-c36-icon\"><!--Icon by Font Awesome (https:\/\/fontawesome.com)-->\n    <svg version=\"1.1\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" xmlns:xlink=\"http:\/\/www.w3.org\/1999\/xlink\" id=\"cogs\" viewBox=\"0 0 1920 1896.0833\">\n      <path d=\"M896 896q0-106-75-181t-181-75-181 75-75 181 75 181 181 75 181-75 75-181zm768 512q0-52-38-90t-90-38-90 38-38 90q0 53 37.5 90.5t90.5 37.5 90.5-37.5 37.5-90.5zm0-1024q0-52-38-90t-90-38-90 38-38 90q0 53 37.5 90.5T1536 512t90.5-37.5T1664 384zm-384 421v185q0 10-7 19.5t-16 10.5l-155 24q-11 35-32 76 34 48 90 115 7 11 7 20 0 12-7 19-23 30-82.5 89.5T999 1423q-11 0-21-7l-115-90q-37 19-77 31-11 108-23 155-7 24-30 24H547q-11 0-20-7.5t-10-17.5l-23-153q-34-10-75-31l-118 89q-7 7-20 7-11 0-21-8-144-133-144-160 0-9 7-19 10-14 41-53t47-61q-23-44-35-82l-152-24q-10-1-17-9.5T0 987V802q0-10 7-19.5T23 772l155-24q11-35 32-76-34-48-90-115-7-11-7-20 0-12 7-20 22-30 82-89t79-59q11 0 21 7l115 90q34-18 77-32 11-108 23-154 7-24 30-24h186q11 0 20 7.5t10 17.5l23 153q34 10 75 31l118-89q8-7 20-7 11 0 21 8 144 133 144 160 0 8-7 19-12 16-42 54t-45 60q23 48 34 82l152 23q10 2 17 10.5t7 19.5zm640 533v140q0 16-149 31-12 27-30 52 51 113 51 138 0 4-4 7-122 71-124 71-8 0-46-47t-52-68q-20 2-30 2t-30-2q-14 21-52 68t-46 47q-2 0-124-71-4-3-4-7 0-25 51-138-18-25-30-52-149-15-149-31v-140q0-16 149-31 13-29 30-52-51-113-51-138 0-4 4-7 4-2 35-20t59-34 30-16q8 0 46 46.5t52 67.5q20-2 30-2t30 2q51-71 92-112l6-2q4 0 124 70 4 3 4 7 0 25-51 138 17 23 30 52 149 15 149 31zm0-1024v140q0 16-149 31-12 27-30 52 51 113 51 138 0 4-4 7-122 71-124 71-8 0-46-47t-52-68q-20 2-30 2t-30-2q-14 21-52 68t-46 47q-2 0-124-71-4-3-4-7 0-25 51-138-18-25-30-52-149-15-149-31V314q0-16 149-31 13-29 30-52-51-113-51-138 0-4 4-7 4-2 35-20t59-34 30-16q8 0 46 46.5t52 67.5q20-2 30-2t30 2q51-71 92-112l6-2q4 0 124 70 4 3 4 7 0 25-51 138 17 23 30 52 149 15 149 31z\"><\/path>\n    <\/svg><\/span><\/div>\n              <div data-colibri-id=\"11649-c37\" class=\"h-global-transition-all h-heading style-11414 style-local-11649-c37 position-relative h-element\">\n                <!---->\n                <div class=\"h-heading__outer style-11414 style-local-11649-c37\">\n                  <!---->\n                  <!---->\n                  <h5 class=\"\"><span style=\"color: rgb(6, 80, 155);\">n8n<\/span><\/h5>\n                <\/div>\n              <\/div>\n              <div data-colibri-id=\"11649-c38\" class=\"h-text h-text-component style-11415 style-local-11649-c38 position-relative h-element\">\n                <!---->\n                <!---->\n                <div class=\"\">\n                  <p><strong style=\"color: rgb(0, 0, 0);\">Workflows automatisieren<\/strong><\/p>\n                  <p><span style=\"color: rgb(0, 0, 0);\">n8n verbindet verschiedene Anwendungen und automatisiert wiederkehrende Aufgaben, ganz ohne Programmierkenntnisse. Ob Datenabgleich, Benachrichtigungen oder Formularverarbeitung: einmal eingerichtet, l\u00e4uft es von selbst.  <\/span><\/p>\n                <\/div>\n              <\/div>\n            <\/div>\n          <\/div>\n        <\/div>\n        <div class=\"h-column h-column-container d-flex h-col-lg-3 h-col-md-3 h-col-12 style-11412-outer style-local-11649-c39-outer\">\n          <div data-colibri-id=\"11649-c39\" class=\"d-flex h-flex-basis h-column__inner h-px-lg-2 h-px-md-3 h-px-2 v-inner-lg-0 v-inner-md-3 v-inner-2 style-11412 style-local-11649-c39 position-relative\">\n            <!---->\n            <!---->\n            <div class=\"w-100 h-y-container h-column__content h-column__v-align flex-basis-100 align-self-lg-start align-self-md-start align-self-start\">\n              <!---->\n              <div data-colibri-id=\"11649-c40\" class=\"h-icon style-11413 style-local-11649-c40 position-relative h-element\">\n                <!----><span class=\"h-svg-icon h-icon__icon style-11413-icon style-local-11649-c40-icon\"><!--Icon by Font Awesome (https:\/\/fontawesome.com)-->\n    <svg version=\"1.1\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" xmlns:xlink=\"http:\/\/www.w3.org\/1999\/xlink\" id=\"file-pdf-o\" viewBox=\"0 0 1611.2499 1896.0833\">\n      <path d=\"M1468 380q28 28 48 76t20 88v1152q0 40-28 68t-68 28H96q-40 0-68-28t-28-68V96q0-40 28-68T96 0h896q40 0 88 20t76 48zm-444-244v376h376q-10-29-22-41l-313-313q-12-12-41-22zm384 1528V640H992q-40 0-68-28t-28-68V128H128v1536h1280zm-514-593q33 26 84 56 59-7 117-7 147 0 177 49 16 22 2 52 0 1-1 2l-2 2v1q-6 38-71 38-48 0-115-20t-130-53q-221 24-392 83-153 262-242 262-15 0-28-7l-24-12q-1-1-6-5-10-10-6-36 9-40 56-91.5t132-96.5q14-9 23 6 2 2 2 4 52-85 107-197 68-136 104-262-24-82-30.5-159.5T657 552q11-40 42-40h22q23 0 35 15 18 21 9 68-2 6-4 8 1 3 1 8v30q-2 123-14 192 55 164 146 238zm-576 411q52-24 137-158-51 40-87.5 84t-49.5 74zm398-920q-15 42-2 132 1-7 7-44 0-3 7-43 1-4 4-8-1-1-1-2-1-2-1-3-1-22-13-36 0 1-1 2v2zm-124 661q135-54 284-81-2-1-13-9.5t-16-13.5q-76-67-127-176-27 86-83 197-30 56-45 83zm646-16q-24-24-140-24 76 28 124 28 14 0 18-1 0-1-2-3z\"><\/path>\n    <\/svg><\/span><\/div>\n              <div data-colibri-id=\"11649-c41\" class=\"h-global-transition-all h-heading style-11414 style-local-11649-c41 position-relative h-element\">\n                <!---->\n                <div class=\"h-heading__outer style-11414 style-local-11649-c41\">\n                  <!---->\n                  <!---->\n                  <h5 class=\"\"><span style=\"color: rgb(6, 80, 155);\">Bento-PDF<\/span><\/h5>\n                <\/div>\n              <\/div>\n              <div data-colibri-id=\"11649-c42\" class=\"h-text h-text-component style-11415 style-local-11649-c42 position-relative h-element\">\n                <!---->\n                <!---->\n                <div class=\"\">\n                  <p><strong style=\"color: rgb(0, 0, 0);\">Workflows automatisieren<\/strong><\/p>\n                  <p><span style=\"color: rgb(0, 0, 0);\">Bento-PDF erledigt alle PDF-Aufgaben direkt im Browser: zusammenf\u00fchren, teilen, komprimieren, konvertieren und mehr. Ohne Server, ohne Upload, alles bleibt lokal auf dem eigenen Ger\u00e4t.<\/span>\n                    <span\n                      style=\"color: rgb(0, 0, 0);\"> <\/span>\n                  <\/p>\n                <\/div>\n              <\/div>\n            <\/div>\n          <\/div>\n        <\/div>\n        <div class=\"h-column h-column-container d-flex h-col-lg-3 h-col-md-3 h-col-12 style-11412-outer style-local-11649-c43-outer\">\n          <div data-colibri-id=\"11649-c43\" class=\"d-flex h-flex-basis h-column__inner h-px-lg-2 h-px-md-3 h-px-2 v-inner-lg-0 v-inner-md-3 v-inner-2 style-11412 style-local-11649-c43 position-relative\">\n            <!---->\n            <!---->\n            <div class=\"w-100 h-y-container h-column__content h-column__v-align flex-basis-100 align-self-lg-start align-self-md-start align-self-start\">\n              <!---->\n              <div data-colibri-id=\"11649-c44\" class=\"h-icon style-11413 style-local-11649-c44 position-relative h-element\">\n                <!----><span class=\"h-svg-icon h-icon__icon style-11413-icon style-local-11649-c44-icon\"><!--Icon by Ionicons (https:\/\/ionicons.com)--><svg version=\"1.1\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" xmlns:xlink=\"http:\/\/www.w3.org\/1999\/xlink\" id=\"document\" viewBox=\"0 0 288 512\"><path d=\"M287 169c1 3 1 5 1 9v244c0 13-9 26-21 26H24c-12 0-24-13-24-26V87c0-13 12-23 24-23h160c3 0 6 0 9 2 2 1 5 2 7 4l83 92c2 2 3 4 4 7zm-94-58v63h58zM32 416h224V206h-67c-15 0-28-13-28-28V96H32v320z\"><\/path><\/svg><\/span><\/div>\n              <div\n                data-colibri-id=\"11649-c45\" class=\"h-global-transition-all h-heading style-11414 style-local-11649-c45 position-relative h-element\">\n                <!---->\n                <div class=\"h-heading__outer style-11414 style-local-11649-c45\">\n                  <!---->\n                  <!---->\n                  <h5 class=\"\"><span style=\"color: rgb(6, 80, 155);\">Documenso<\/span><\/h5>\n                <\/div>\n            <\/div>\n            <div data-colibri-id=\"11649-c46\" class=\"h-text h-text-component style-11415 style-local-11649-c46 position-relative h-element\">\n              <!---->\n              <!---->\n              <div class=\"\">\n                <p><strong style=\"color: rgb(0, 0, 0);\">Dokumente digital unterschreiben<\/strong><\/p>\n                <p><span style=\"color: rgb(0, 0, 0);\">Documenso erm\u00f6glicht rechtsg\u00fcltige elektronische Unterschriften f\u00fcr Vertr\u00e4ge und Dokumente, ganz ohne Papier und Postweg. Der Unterschriftsprozess l\u00e4sst sich einfach nachverfolgen.<\/span> <span style=\"color: rgb(0, 0, 0);\"> <\/span><\/p>\n              <\/div>\n            <\/div>\n          <\/div>\n        <\/div>\n      <\/div>\n      <div class=\"h-column h-column-container d-flex h-col-lg-3 h-col-md-3 h-col-12 style-11412-outer style-local-11649-c47-outer\">\n        <div data-colibri-id=\"11649-c47\" class=\"d-flex h-flex-basis h-column__inner h-px-lg-2 h-px-md-3 h-px-2 v-inner-lg-0 v-inner-md-3 v-inner-2 style-11412 style-local-11649-c47 position-relative\">\n          <!---->\n          <!---->\n          <div class=\"w-100 h-y-container h-column__content h-column__v-align flex-basis-100 align-self-lg-start align-self-md-start align-self-start\">\n            <!---->\n            <div data-colibri-id=\"11649-c48\" class=\"h-icon style-11413 style-local-11649-c48 position-relative h-element\">\n              <!----><span class=\"h-svg-icon h-icon__icon style-11413-icon style-local-11649-c48-icon\"><!--Icon by Font Awesome (https:\/\/fontawesome.com)-->\n    <svg version=\"1.1\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" xmlns:xlink=\"http:\/\/www.w3.org\/1999\/xlink\" id=\"gavel\" viewBox=\"0 0 1792 1896.0833\">\n      <path d=\"M1771 1536q0 53-37 90l-107 108q-39 37-91 37-53 0-90-37l-363-364q-38-36-38-90 0-53 43-96L832 928l-126 126q-14 14-34 14t-34-14q2 2 12.5 12t12.5 13 10 11.5 10 13.5 6 13.5 5.5 16.5 1.5 18q0 38-28 68-3 3-16.5 18t-19 20.5T614 1275t-22 15.5-22 9-26 4.5q-40 0-68-28L68 868q-28-28-28-68 0-13 4.5-26t9-22T69 730t16.5-18.5 20.5-19 18-16.5q30-28 68-28 10 0 18 1.5t16.5 5.5 13.5 6 13.5 10 11.5 10 13 12.5 12 12.5q-14-14-14-34t14-34l348-348q14-14 34-14t34 14q-2-2-12.5-12T681 265t-10-11.5-10-13.5-6-13.5-5.5-16.5-1.5-18q0-38 28-68 3-3 16.5-18t19-20.5T730 69t22-15.5 22-9 26-4.5q40 0 68 28l408 408q28 28 28 68 0 13-4.5 26t-9 22-15.5 22-16.5 18.5-20.5 19-18 16.5q-30 28-68 28-10 0-18-1.5t-16.5-5.5-13.5-6-13.5-10-11.5-10-13-12.5-12-12.5q14 14 14 34t-14 34L928 832l256 256q43-43 96-43 52 0 91 37l363 363q37 39 37 91z\"><\/path>\n    <\/svg><\/span><\/div>\n            <div data-colibri-id=\"11649-c49\" class=\"h-global-transition-all h-heading style-11414 style-local-11649-c49 position-relative h-element\">\n              <!---->\n              <div class=\"h-heading__outer style-11414 style-local-11649-c49\">\n                <!---->\n                <!---->\n                <h5 class=\"\"><span style=\"color: rgb(6, 80, 155);\">IT-Tools<\/span><\/h5>\n              <\/div>\n            <\/div>\n            <div data-colibri-id=\"11649-c50\" class=\"h-text h-text-component style-11415 style-local-11649-c50 position-relative h-element\">\n              <!---->\n              <!---->\n              <div class=\"\">\n                <p><strong style=\"color: rgb(0, 0, 0);\">Praktische Werkzeuge f\u00fcr den Arbeitsalltag<\/strong><\/p>\n                <p><span style=\"color: rgb(0, 0, 0);\">IT-Tools b\u00fcndelt n\u00fctzliche Helfer wie Passwort-Generator, Konverter, Hash-Rechner und viele weitere kleine Tools an einem Ort. Kein Suchen mehr nach einzelnen Online-Diensten f\u00fcr Alltagsaufgaben.<\/span>                  <span style=\"color: rgb(0, 0, 0);\"> <\/span><\/p>\n              <\/div>\n            <\/div>\n          <\/div>\n        <\/div>\n      <\/div>\n      <div class=\"h-column h-column-container d-flex h-col-lg-3 h-col-md-3 h-col-12 style-11412-outer style-local-11649-c51-outer\">\n        <div data-colibri-id=\"11649-c51\" class=\"d-flex h-flex-basis h-column__inner h-px-lg-2 h-px-md-3 h-px-2 v-inner-lg-0 v-inner-md-3 v-inner-2 style-11412 style-local-11649-c51 position-relative\">\n          <!---->\n          <!---->\n          <div class=\"w-100 h-y-container h-column__content h-column__v-align flex-basis-100 align-self-lg-start align-self-md-start align-self-start\">\n            <!---->\n            <div data-colibri-id=\"11649-c52\" class=\"h-icon style-11413 style-local-11649-c52 position-relative h-element\">\n              <!----><span class=\"h-svg-icon h-icon__icon style-11413-icon style-local-11649-c52-icon\"><!--Icon by Simple Line Icons (https:\/\/github.com\/thesabbir\/simple-line-icons)--><svg version=\"1.1\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" xmlns:xlink=\"http:\/\/www.w3.org\/1999\/xlink\" id=\"bubbles\" viewBox=\"0 0 512.344 514.032\"><path d=\"M212.584 340.688c115.472 0 204.952-65.952 203.576-163.816 0-86.592-91.608-155.816-207.08-155.816C93.592 21.056 0 90.28 0 176.872c0 53.936 26.456 111.44 81.704 139.688 0 .328-.096.576-.096.936 0 23.44-19.512 55.576-27.2 68.832h.032c-.608 1.44-.976 3-.976 4.672 0 6.56 5.288 11.832 11.848 11.832.968 0 2.544-.2 3.112-.2.16 0 .216 0 .2.048 40.832-6.672 101.128-52.624 110.064-63.512 9.168 1.36 15.36 1.576 23.04 1.576 3.264-.008 6.736-.056 10.856-.056zm-47.064-34.44l-20.488 17.016c-4.752 5.488-25.4 22.72-43.176 33.904 10.824-30.84 10.352-40.608 10.352-40.608l1.504-19.576-17.44-8.904c-44.336-22.672-64.264-69.872-64.264-111.2 0-68.832 79.432-123.816 177.08-123.816 97.64 0 175.08 54.984 175.08 123.816-.304 76.304-72.936 132.312-170.576 132.312 0 0-14.904.576-30.2-1.688l-17.872-1.256zm346.824-.136c0-43.368-16.944-76.44-59.224-101.256-1.032 11.536-4.32 23.912-7.896 34.784 27.328 18.888 35.128 38.28 35.128 66.472 0 34.512-16.08 59.544-53.456 78.672l-15.92 7.904s1.656 41 4.112 51.376c-31.224-22.888-41.952-42-41.952-42l-16.832 2.592c-6.656.968-24.656.984-24.656.984-43.472 0-75.688-10.36-103.168-31.872 7.464-.456-44.592-.44-45.752.576 31.784 38.816 83.736 63.296 148.92 63.296 3.544 0 6.544.032 9.36.032 6.64 0 12-.184 19.92-1.344 7.744 9.392 51.112 50.752 86.408 56.504-.016-.032.032-.032.184-.032.472 0 1.84.168 2.672.168 5.672 0 10.248-4.56 10.248-10.232 0-1.44-.328-2.8-.84-4.032h.032c-6.64-11.44-17.064-44.872-17.064-65.128 0-.312-.096-.528-.096-.816 47.752-24.416 69.872-60.04 69.872-106.648z\"><\/path><\/svg><\/span><\/div>\n            <div\n              data-colibri-id=\"11649-c53\" class=\"h-global-transition-all h-heading style-11414 style-local-11649-c53 position-relative h-element\">\n              <!---->\n              <div class=\"h-heading__outer style-11414 style-local-11649-c53\">\n                <!---->\n                <!---->\n                <h5 class=\"\"><span style=\"color: rgb(6, 80, 155);\">Libre-Translate<\/span><\/h5>\n              <\/div>\n          <\/div>\n          <div data-colibri-id=\"11649-c54\" class=\"h-text h-text-component style-11415 style-local-11649-c54 position-relative h-element\">\n            <!---->\n            <!---->\n            <div class=\"\">\n              <p><span style=\"font-weight: 400; color: rgb(0, 0, 0);\">\u00dcbersetzungen selbst hosten<\/span><\/p>\n              <p><span style=\"color: rgb(0, 0, 0);\">LibreTranslate \u00fcbersetzt Texte zwischen zahlreichen Sprachen, komplett selbst gehostet und ohne Daten an Dritte weiterzugeben. Ideal f\u00fcr vertrauliche Dokumente oder Kommunikation, die nicht in fremde Cloud-Dienste soll.<\/span>                <span style=\"color: rgb(0, 0, 0);\"> <\/span><\/p>\n            <\/div>\n          <\/div>\n        <\/div>\n      <\/div>\n    <\/div>\n  <\/div>\n<\/div>\n<\/div>\n<\/div>\n<div data-colibri-component=\"section\" data-colibri-id=\"11649-c55\" id=\"custom\" class=\"h-section h-section-global-spacing d-flex align-items-lg-center align-items-md-center align-items-center style-11400 style-local-11649-c55 position-relative\">\n  <!---->\n  <!---->\n  <div class=\"h-section-grid-container h-section-boxed-container\">\n    <!---->\n    <div data-colibri-id=\"11649-c56\" class=\"h-row-container gutters-row-lg-2 gutters-row-md-2 gutters-row-0 gutters-row-v-lg-2 gutters-row-v-md-2 gutters-row-v-2 style-11401 style-local-11649-c56 position-relative\">\n      <!---->\n      <div class=\"h-row justify-content-lg-center justify-content-md-center justify-content-center align-items-lg-stretch align-items-md-stretch align-items-stretch gutters-col-lg-2 gutters-col-md-2 gutters-col-0 gutters-col-v-lg-2 gutters-col-v-md-2 gutters-col-v-2\">\n        <!---->\n        <div class=\"h-column h-column-container d-flex h-col-lg-auto h-col-md-auto h-col-auto style-11402-outer style-local-11649-c57-outer\">\n          <div data-colibri-id=\"11649-c57\" class=\"d-flex h-flex-basis h-column__inner h-px-lg-2 h-px-md-2 h-px-2 v-inner-lg-2 v-inner-md-2 v-inner-2 style-11402 style-local-11649-c57 position-relative\">\n            <!---->\n            <!---->\n            <div class=\"w-100 h-y-container h-column__content h-column__v-align flex-basis-100 align-self-lg-start align-self-md-start align-self-start\">\n              <!---->\n              <div data-colibri-id=\"11649-c58\" class=\"h-text h-text-component style-11238 style-local-11649-c58 position-relative h-element\">\n                <!---->\n                <!---->\n                <div class=\"\">\n                  <h3><span style=\"color: rgb(6, 80, 155);\">Unsere Leistungen im \u00dcberblick<\/span><\/h3>\n                  <ul>\n                    <li><span style=\"color: rgb(6, 80, 155);\">Ma\u00dfgeschneiderter virtueller Server, exklusiv f\u00fcr Ihr Unternehmen<\/span><\/li>\n                    <li><span style=\"color: rgb(6, 80, 155);\">Vollst\u00e4ndig verwaltete Business-Cloud auf Open-Source-Basis<\/span><\/li>\n                    <li><span style=\"color: rgb(6, 80, 155);\">Private Cloud, Daten bleiben in Deutschland<\/span><\/li>\n                    <li><span style=\"color: rgb(6, 80, 155);\">Ausschlie\u00dflich gepr\u00fcfte Open-Source-Anwendungen<\/span><\/li>\n                    <li><span style=\"color: rgb(6, 80, 155);\">Keine Lizenzkosten, modernste Software inklusive<\/span><\/li>\n                  <\/ul>\n                  <p>\n                    <br>\n                  <\/p>\n                <\/div>\n              <\/div>\n            <\/div>\n          <\/div>\n        <\/div>\n      <\/div>\n    <\/div>\n  <\/div>\n<\/div>\n<\/div>","protected":false},"excerpt":{"rendered":"<p>Cloud Business Apps Mit unseren Cloud Business Apps bieten wir eine vollst\u00e4ndig verwaltete Business-Cloud auf Open-Source-Basis, die exklusiv f\u00fcr Ihr Unternehmen in einer Private Cloud l\u00e4uft. Das bedeutet: modernste Software, keine Lizenzkosten, Daten bleiben in Deutschland und wir k\u00fcmmern uns um alles im Hintergrund. Was Sie bekommen: Dateiablage, Passwort Management, digitale Dokumentenmanagement, Workflow Automatisierung, PDF-Bearbeitung [&hellip;]<\/p>\n","protected":false},"author":9,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"page-templates\/full-width-page.php","meta":{"footnotes":""},"class_list":["post-11649","page","type-page","status-publish","hentry"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.5 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Cloud Business Apps - GMZ Informationstechnik GmbH<\/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:\/\/www.gmz-it.de\/index.php\/cloud-business-apps\/\" \/>\n<meta property=\"og:locale\" content=\"de_DE\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Cloud Business Apps - GMZ Informationstechnik GmbH\" \/>\n<meta property=\"og:description\" content=\"Cloud Business Apps Mit unseren Cloud Business Apps bieten wir eine vollst\u00e4ndig verwaltete Business-Cloud auf Open-Source-Basis, die exklusiv f\u00fcr Ihr Unternehmen in einer Private Cloud l\u00e4uft. Das bedeutet: modernste Software, keine Lizenzkosten, Daten bleiben in Deutschland und wir k\u00fcmmern uns um alles im Hintergrund. Was Sie bekommen: Dateiablage, Passwort Management, digitale Dokumentenmanagement, Workflow Automatisierung, PDF-Bearbeitung [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.gmz-it.de\/index.php\/cloud-business-apps\/\" \/>\n<meta property=\"og:site_name\" content=\"GMZ Informationstechnik GmbH\" \/>\n<meta property=\"article:modified_time\" content=\"2026-09-02T08:46:14+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.gmz-it.de\/wp-content\/uploads\/2026\/06\/cropped-cropped-Dashboard-Cloud-Business-Apps.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1078\" \/>\n\t<meta property=\"og:image:height\" content=\"1349\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Gesch\u00e4tzte Lesezeit\" \/>\n\t<meta name=\"twitter:data1\" content=\"3\u00a0Minuten\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.gmz-it.de\\\/index.php\\\/cloud-business-apps\\\/\",\"url\":\"https:\\\/\\\/www.gmz-it.de\\\/index.php\\\/cloud-business-apps\\\/\",\"name\":\"Cloud Business Apps - GMZ Informationstechnik GmbH\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.gmz-it.de\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.gmz-it.de\\\/index.php\\\/cloud-business-apps\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.gmz-it.de\\\/index.php\\\/cloud-business-apps\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.gmz-it.de\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/cropped-cropped-Dashboard-Cloud-Business-Apps.png\",\"datePublished\":\"2026-06-19T07:59:21+00:00\",\"dateModified\":\"2026-09-02T08:46:14+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.gmz-it.de\\\/index.php\\\/cloud-business-apps\\\/#breadcrumb\"},\"inLanguage\":\"de\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.gmz-it.de\\\/index.php\\\/cloud-business-apps\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\\\/\\\/www.gmz-it.de\\\/index.php\\\/cloud-business-apps\\\/#primaryimage\",\"url\":\"https:\\\/\\\/www.gmz-it.de\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/cropped-cropped-Dashboard-Cloud-Business-Apps.png\",\"contentUrl\":\"https:\\\/\\\/www.gmz-it.de\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/cropped-cropped-Dashboard-Cloud-Business-Apps.png\",\"width\":1078,\"height\":1349},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.gmz-it.de\\\/index.php\\\/cloud-business-apps\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Startseite\",\"item\":\"https:\\\/\\\/www.gmz-it.de\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Cloud Business Apps\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.gmz-it.de\\\/#website\",\"url\":\"https:\\\/\\\/www.gmz-it.de\\\/\",\"name\":\"GMZ Informationstechnik GmbH\",\"description\":\"Your IT is... where you need it\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.gmz-it.de\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.gmz-it.de\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"de\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/www.gmz-it.de\\\/#organization\",\"name\":\"GMZ Informationstechnik GmbH\",\"url\":\"https:\\\/\\\/www.gmz-it.de\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\\\/\\\/www.gmz-it.de\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/www.gmz-it.de\\\/wp-content\\\/uploads\\\/2020\\\/07\\\/cropped-GMZ_Logo-Kopf-Office-2.jpg\",\"contentUrl\":\"https:\\\/\\\/www.gmz-it.de\\\/wp-content\\\/uploads\\\/2020\\\/07\\\/cropped-GMZ_Logo-Kopf-Office-2.jpg\",\"width\":1018,\"height\":438,\"caption\":\"GMZ Informationstechnik GmbH\"},\"image\":{\"@id\":\"https:\\\/\\\/www.gmz-it.de\\\/#\\\/schema\\\/logo\\\/image\\\/\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Cloud Business Apps - GMZ Informationstechnik GmbH","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:\/\/www.gmz-it.de\/index.php\/cloud-business-apps\/","og_locale":"de_DE","og_type":"article","og_title":"Cloud Business Apps - GMZ Informationstechnik GmbH","og_description":"Cloud Business Apps Mit unseren Cloud Business Apps bieten wir eine vollst\u00e4ndig verwaltete Business-Cloud auf Open-Source-Basis, die exklusiv f\u00fcr Ihr Unternehmen in einer Private Cloud l\u00e4uft. Das bedeutet: modernste Software, keine Lizenzkosten, Daten bleiben in Deutschland und wir k\u00fcmmern uns um alles im Hintergrund. Was Sie bekommen: Dateiablage, Passwort Management, digitale Dokumentenmanagement, Workflow Automatisierung, PDF-Bearbeitung [&hellip;]","og_url":"https:\/\/www.gmz-it.de\/index.php\/cloud-business-apps\/","og_site_name":"GMZ Informationstechnik GmbH","article_modified_time":"2026-09-02T08:46:14+00:00","og_image":[{"width":1078,"height":1349,"url":"https:\/\/www.gmz-it.de\/wp-content\/uploads\/2026\/06\/cropped-cropped-Dashboard-Cloud-Business-Apps.png","type":"image\/png"}],"twitter_card":"summary_large_image","twitter_misc":{"Gesch\u00e4tzte Lesezeit":"3\u00a0Minuten"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/www.gmz-it.de\/index.php\/cloud-business-apps\/","url":"https:\/\/www.gmz-it.de\/index.php\/cloud-business-apps\/","name":"Cloud Business Apps - GMZ Informationstechnik GmbH","isPartOf":{"@id":"https:\/\/www.gmz-it.de\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.gmz-it.de\/index.php\/cloud-business-apps\/#primaryimage"},"image":{"@id":"https:\/\/www.gmz-it.de\/index.php\/cloud-business-apps\/#primaryimage"},"thumbnailUrl":"https:\/\/www.gmz-it.de\/wp-content\/uploads\/2026\/06\/cropped-cropped-Dashboard-Cloud-Business-Apps.png","datePublished":"2026-06-19T07:59:21+00:00","dateModified":"2026-09-02T08:46:14+00:00","breadcrumb":{"@id":"https:\/\/www.gmz-it.de\/index.php\/cloud-business-apps\/#breadcrumb"},"inLanguage":"de","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.gmz-it.de\/index.php\/cloud-business-apps\/"]}]},{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/www.gmz-it.de\/index.php\/cloud-business-apps\/#primaryimage","url":"https:\/\/www.gmz-it.de\/wp-content\/uploads\/2026\/06\/cropped-cropped-Dashboard-Cloud-Business-Apps.png","contentUrl":"https:\/\/www.gmz-it.de\/wp-content\/uploads\/2026\/06\/cropped-cropped-Dashboard-Cloud-Business-Apps.png","width":1078,"height":1349},{"@type":"BreadcrumbList","@id":"https:\/\/www.gmz-it.de\/index.php\/cloud-business-apps\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Startseite","item":"https:\/\/www.gmz-it.de\/"},{"@type":"ListItem","position":2,"name":"Cloud Business Apps"}]},{"@type":"WebSite","@id":"https:\/\/www.gmz-it.de\/#website","url":"https:\/\/www.gmz-it.de\/","name":"GMZ Informationstechnik GmbH","description":"Your IT is... where you need it","publisher":{"@id":"https:\/\/www.gmz-it.de\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.gmz-it.de\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"de"},{"@type":"Organization","@id":"https:\/\/www.gmz-it.de\/#organization","name":"GMZ Informationstechnik GmbH","url":"https:\/\/www.gmz-it.de\/","logo":{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/www.gmz-it.de\/#\/schema\/logo\/image\/","url":"https:\/\/www.gmz-it.de\/wp-content\/uploads\/2020\/07\/cropped-GMZ_Logo-Kopf-Office-2.jpg","contentUrl":"https:\/\/www.gmz-it.de\/wp-content\/uploads\/2020\/07\/cropped-GMZ_Logo-Kopf-Office-2.jpg","width":1018,"height":438,"caption":"GMZ Informationstechnik GmbH"},"image":{"@id":"https:\/\/www.gmz-it.de\/#\/schema\/logo\/image\/"}}]}},"_links":{"self":[{"href":"https:\/\/www.gmz-it.de\/index.php\/wp-json\/wp\/v2\/pages\/11649","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.gmz-it.de\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.gmz-it.de\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.gmz-it.de\/index.php\/wp-json\/wp\/v2\/users\/9"}],"replies":[{"embeddable":true,"href":"https:\/\/www.gmz-it.de\/index.php\/wp-json\/wp\/v2\/comments?post=11649"}],"version-history":[{"count":10,"href":"https:\/\/www.gmz-it.de\/index.php\/wp-json\/wp\/v2\/pages\/11649\/revisions"}],"predecessor-version":[{"id":12380,"href":"https:\/\/www.gmz-it.de\/index.php\/wp-json\/wp\/v2\/pages\/11649\/revisions\/12380"}],"wp:attachment":[{"href":"https:\/\/www.gmz-it.de\/index.php\/wp-json\/wp\/v2\/media?parent=11649"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}