diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000000000000000000000000000000000000..9ab59b180fb670006ac910a2bc4524a1376dcea0
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,15 @@
+
+# Ignore developer files when exporting
+.gitattributes         export-ignore
+.gitignore             export-ignore
+.gitlab-ci.yml         export-ignore
+.phive                 export-ignore
+captainhook.json       export-ignore
+phpcs.xml.dist         export-ignore
+phpstan-baseline.neon  export-ignore
+phpstan.dist.neon      export-ignore
+phpunit.dist.xml       export-ignore
+tests                  export-ignore
+
+# Explicitly set file type and line endings for PHP files, improves git diff output
+*.php     text eol=lf diff=php
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index f4da20891502a3992abf2981d8f6c46e33868ab6..e25391c1f85364060ac958b37e8e35c6c51beafe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,11 @@
 tools/
 phpstan.neon
 .phpunit.result.cache
-phpunit.xml
\ No newline at end of file
+phpunit.xml
+tools/
+
+phpstan.neon
+
+.phpunit.result.cache
+
+phpunit.xml
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b8d20acb6c365ad8036a7b36d47dabd876d08efa..b5a64b401e554341447c74d7cf93a89ac95a3fdb 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,3 +1,17 @@
 include:
-  - project: 'quiqqer/stabilization/semantic-release'
-    file: '/ci-templates/.gitlab-ci.yml'
+  - component: dev.quiqqer.com/quiqqer/stabilization/ci-cd-components/quiqqer-package-bundle/quiqqer-package-bundle@main
+
+# Remove the entire phpunit-php8.1 block, to allow PHPUnit to run on PHP 8.1 in your pipeline
+phpunit-php8.1:
+  rules:
+    - when: never
+
+# Remove the entire phpunit-php8.2 block, to allow PHPUnit to run on PHP 8.2 in your pipeline
+phpunit-php8.2:
+  rules:
+    - when: never
+
+# Remove the entire phpunit-php8.3 block, to allow PHPUnit to run on PHP 8.3 in your pipeline
+phpunit-php8.3:
+  rules:
+    - when: never
\ No newline at end of file
diff --git a/.phive/phars.xml b/.phive/phars.xml
index a1315a09b4adad780a9c5e52f74835c708c5c7d5..5bfa092bfad10dad9d23240281a5a2041acb815b 100644
--- a/.phive/phars.xml
+++ b/.phive/phars.xml
@@ -1,4 +1,8 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <phive xmlns="https://phar.io/phive">
-  <phar name="phpstan" version="^1.10.67" installed="1.10.67" location="./tools/phpstan" copy="false"/>
+  <phar name="phpstan" version="1.11.8" installed="1.11.8" location="./tools/phpstan" copy="false"/>
+  <phar name="phpunit" version="^10.5.20" installed="10.5.20" location="./tools/phpunit" copy="false"/>
+  <phar name="phpcs" version="^3.10.1" installed="3.10.1" location="./tools/phpcs" copy="false"/>
+  <phar name="phpcbf" version="^3.10.1" installed="3.10.1" location="./tools/phpcbf" copy="false"/>
+  <phar name="captainhook" version="^5.23.3" installed="5.23.3" location="./tools/captainhook" copy="false"/>
 </phive>
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000000000000000000000000000000000000..4a69a59b440e5beec561eca1e341509bd5a18688
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,3 @@
+# Contributing
+
+This package follows the [QUIQQER contribution guidelines](https://dev.quiqqer.com/quiqqer/stabilization/documentation/-/wikis/home).
\ No newline at end of file
diff --git a/captainhook.json b/captainhook.json
new file mode 100644
index 0000000000000000000000000000000000000000..3702e1a358868bedd5ff4c7eae40bb1abb589267
--- /dev/null
+++ b/captainhook.json
@@ -0,0 +1,13 @@
+{
+    "pre-commit": {
+        "enabled": true,
+        "actions": [
+            {
+                "action": "\\CaptainHook\\App\\Hook\\PHP\\Action\\Linting"
+            },
+            {
+                "action": "composer test"
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/composer.json b/composer.json
index bf6130980ce9750f00550504981b2c0d3189570a..041156850576f33e26fd1c65e25f13cddbd47d5a 100644
--- a/composer.json
+++ b/composer.json
@@ -1,30 +1,72 @@
 {
-  "name": "quiqqer/menu",
-  "type": "quiqqer-module",
-  "description": "The Plugin menu provides various menu controls.",
-  "license": [
-    "GPL-3.0+",
-    "PCSG QL-1.0"
-  ],
-  "authors": [
-    {
-      "name": "Henning Leutz",
-      "email": "leutz@pcsg.de",
-      "homepage": "https://www.pcsg.de",
-      "role": "Developer"
+    "name": "quiqqer/menu",
+    "type": "quiqqer-module",
+    "description": "The Plugin menu provides various menu controls.",
+    "license": [
+        "GPL-3.0+",
+        "PCSG QL-1.0"
+    ],
+    "authors": [
+        {
+            "name": "Henning Leutz",
+            "email": "leutz@pcsg.de",
+            "homepage": "https://www.pcsg.de",
+            "role": "Developer"
+        }
+    ],
+    "support": {
+        "email": "support@pcsg.de",
+        "url": "https://www.pcsg.de"
+    },
+    "require": {
+        "php": "^8.1",
+        "quiqqer/core": "^2"
+    },
+    "autoload": {
+        "psr-4": {
+            "QUI\\Menu\\": "src/QUI/Menu"
+        }
+    },
+    "scripts": {
+        "test": [
+            "@dev:lint",
+            "@dev:phpunit"
+        ],
+        "dev:phpunit": "./tools/phpunit",
+        "dev:lint": [
+            "@dev:lint:phpstan",
+            "@dev:lint:style"
+        ],
+        "dev:lint:phpstan": "./tools/phpstan",
+        "dev:lint:style": "./tools/phpcs",
+        "dev:lint:style:fix": "./tools/phpcbf",
+        "dev:init": [
+            "@dev:init:check-requirements",
+            "@dev:init:tools",
+            "@dev:init:git-hooks"
+        ],
+        "dev:init:check-requirements": [
+            "which composer > /dev/null || (echo 'Error: composer has to be globally installed'; exit 1)",
+            "which phive > /dev/null || (echo 'Error: PHIVE has to be globally installed'; exit 1)"
+        ],
+        "dev:init:tools": "phive install --temporary",
+        "dev:init:git-hooks": "./tools/captainhook install --only-enabled --force"
+    },
+    "scripts-aliases": {
+        "test": [
+            "dev:test"
+        ]
+    },
+    "scripts-descriptions": {
+        "test": "Runs linting, static analysis, and unit tests.",
+        "dev:phpunit": "Run PHPUnit test suites",
+        "dev:lint": "Run PHPStan and code style check",
+        "dev:lint:phpstan": "Run PHPStan",
+        "dev:lint:style": "Run code style check (PHP_CodeSniffer)",
+        "dev:lint:style:fix": "Try to fix code style errors automatically",
+        "dev:init": "Initialize the developer tooling (tools and git hooks)",
+        "dev:init:check-requirements": "Check if the necessary requirements are met",
+        "dev:init:tools": "Install all developer tools (requires PHIVE)",
+        "dev:init:git-hooks": "Install all git hooks (may require tools to be installed)"
     }
-  ],
-  "support": {
-    "email": "support@pcsg.de",
-    "url": "https://www.pcsg.de"
-  },
-  "require": {
-    "php": "^8.1",
-    "quiqqer/core": "^2"
-  },
-  "autoload": {
-    "psr-4": {
-      "QUI\\Menu\\": "src/QUI/Menu"
-    }
-  }
 }
\ No newline at end of file
diff --git a/events.xml b/events.xml
index 195edba75261b32235d33445490177b17caef227..5a506592e89fb372e82c9a52d5db19fae0ccf5ea 100644
--- a/events.xml
+++ b/events.xml
@@ -5,4 +5,5 @@
     <event on="onSmartyInit" fire="\QUI\Menu\EventHandler::onSmartyInit"/>
     <event on="onAdminLoadFooter" fire="\QUI\Menu\EventHandler::onAdminLoadFooter"/>
     <event on="onQuiqqerMenuIndependentClear" fire="\QUI\Menu\EventHandler::onQuiqqerMenuIndependentClear"/>
+    <event on="onQuiqqer::template::body::end" fire="\QUI\Menu\EventHandler::onQuiqqerTemplateBodyEnd"/>
 </events>
diff --git a/locale.xml b/locale.xml
index 076bd89c5d766c12575ca98c4c85b6b38b78f653..378062edd836cc844195789c822ca113607a4267 100644
--- a/locale.xml
+++ b/locale.xml
@@ -719,6 +719,53 @@
             <de><![CDATA[Das Menü wurde erfolgreich gespeichert.]]></de>
             <en><![CDATA[The menu has been saved successfully.]]></en>
         </locale>
+
+        <!-- floating menu -->
+        <locale name="floatingMenu.settings.title">
+            <de><![CDATA[Schwebendes Menü]]></de>
+            <en><![CDATA[Floated menu]]></en>
+        </locale>
+        <locale name="floatingMenu.settings.desc">
+            <de><![CDATA[Ein "Schwebendes Menü" ist ein Menü, das sich über den Inhalt der Seite legt und dabei oft an einer bestimmten Position "schwebt" oder fixiert bleibt, selbst wenn man nach unten scrollt. Dieses Menü kann z.B. am Rand des Bildschirms oder in einer Ecke platziert sein und bleibt sichtbar, damit der Nutzer jederzeit schnell darauf zugreifen kann.]]></de>
+            <en><![CDATA[A "Floating menu" is a menu that is superimposed over the content of the page and often "floats" or remains fixed in a certain position, even when scrolling down. This menu can be placed at the edge of the screen or in a corner, for example, and remains visible so that the user can access it quickly at any time.]]></en>
+        </locale>
+
+        <locale name="floatingMenu.settings.menuId">
+            <de><![CDATA[Menu ID]]></de>
+            <en><![CDATA[Menu ID]]></en>
+        </locale>
+        <locale name="floatingMenu.settings.menuId.desc">
+            <de><![CDATA[Wenn hier ein Menü ausgewählt wird, wird es automatisch aktiviert und für die Benutzer auf der Seite sichtbar. Zuvor muss ein Menü unter <i>Einstellungen → Menü Verwaltung</i> erstellt werden. Möchte man das schwebende Menü deaktivieren, muss lediglich das ausgewählte Menü durch Klick auf den X Button entfernt werden.]]></de>
+            <en><![CDATA[If a menu is selected here, it is automatically activated and visible to users on the page. A menu must first be created under <i>Settings → Menu management</i>. If you want to deactivate the floating menu, simply remove the selected menu by clicking on the X button.]]></en>
+        </locale>
+        <locale name="floatingMenu.settings.showLangSwitch">
+            <de><![CDATA[Sprachewechsel aktivieren]]></de>
+            <en><![CDATA[Enable lang switch]]></en>
+        </locale>
+        <locale name="floatingMenu.settings.showLangSwitch.desc">
+            <de><![CDATA[Wenn das Projekt mehrsprachig ist, kann man mit dieser Option den Sprachwechsel aktivieren.]]></de>
+            <en><![CDATA[If the project is multilingual, you can use this option to enable language switching.]]></en>
+        </locale>
+        <locale name="floatingMenu.settings.toggleButton">
+            <de><![CDATA[Umschaltbutton]]></de>
+            <en><![CDATA[Toggle button]]></en>
+        </locale>
+        <locale name="floatingMenu.settings.toggleButton.desc">
+            <de><![CDATA[Der Umschaltbutton zeigt und versteckt die schwebende Navigation. Mit dieser Option kann festgelegt werden, wann er angezeigt wird oder ob er vollständig verborgen bleibt.]]></de>
+            <en><![CDATA[The toggle button shows and hides the floating navigation. This option allows you to specify when it is displayed or if it remains completely hidden.]]></en>
+        </locale>
+        <locale name="floatingMenu.settings.toggleButton.mobile">
+            <de><![CDATA[Nur mobile anzeigen]]></de>
+            <en><![CDATA[Show only mobile]]></en>
+        </locale>
+        <locale name="floatingMenu.settings.toggleButton.always">
+            <de><![CDATA[Mobile und Desktop anzeigen]]></de>
+            <en><![CDATA[Show on mobile and on desktop]]></en>
+        </locale>
+        <locale name="floatingMenu.settings.toggleButton.hide">
+            <de><![CDATA[Deaktivieren]]></de>
+            <en><![CDATA[Disabled]]></en>
+        </locale>
     </groups>
 
     <groups name="quiqqer/menu" datatype="js">
diff --git a/phpcs.xml.dist b/phpcs.xml.dist
new file mode 100644
index 0000000000000000000000000000000000000000..d48084fbdc86a2dbfb78dd89ab2e0b0ff2322ba7
--- /dev/null
+++ b/phpcs.xml.dist
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<ruleset>
+    <!-- Use PSR-12 ruleset -->
+    <rule ref="PSR12"/>
+
+    <!-- Only scan *.php files -->
+    <arg name="extensions" value="php"/>
+
+    <!-- Ignore warnings -->
+    <arg name="warning-severity" value="0"/>
+
+    <!-- Process 64 (or number of CPU cores) files in parallel -->
+    <arg name="parallel" value="64"/>
+
+    <!-- Output relative file paths, by setting the current folder as the basepath -->
+    <arg name="basepath" value="."/>
+
+    <!-- Show colored output -->
+    <arg name="colors"/>
+
+    <!-- Scan everything in the current folder -->
+    <file>.</file>
+</ruleset>
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..21535088260aa3415906fbab59b8c921aec9b5dc 100644
--- a/phpstan-baseline.neon
+++ b/phpstan-baseline.neon
@@ -0,0 +1,26 @@
+parameters:
+	ignoreErrors:
+		-
+			message: "#^Strict comparison using \\=\\=\\= between mixed and '' will always evaluate to false\\.$#"
+			count: 1
+			path: src/QUI/Menu/Bricks/Submenu.php
+
+		-
+			message: "#^Call to an undefined method QUI\\\\Interfaces\\\\Projects\\\\Site\\:\\:getCachePath\\(\\)\\.$#"
+			count: 1
+			path: src/QUI/Menu/DropDownMenu.php
+
+		-
+			message: "#^Parameter \\#1 \\$menuId of static method QUI\\\\Menu\\\\Independent\\\\Handler\\:\\:getMenu\\(\\) expects int, string\\|false given\\.$#"
+			count: 1
+			path: src/QUI/Menu/Independent/Factory.php
+
+		-
+			message: "#^Call to an undefined method QUI\\\\Interfaces\\\\Projects\\\\Site\\:\\:getCachePath\\(\\)\\.$#"
+			count: 1
+			path: src/QUI/Menu/MegaMenu.php
+
+		-
+			message: "#^Variable \\$Site in PHPDoc tag @var does not match any variable in the foreach loop\\: \\$data, \\$dataSet$#"
+			count: 1
+			path: src/QUI/Menu/NavTabs.php
diff --git a/phpstan.dist.neon b/phpstan.dist.neon
index a545a041a3c5b0c758d99bf8c1c49b2a83252eaa..d6e0dec5f5256315b141c310dc6ee629e0133c31 100644
--- a/phpstan.dist.neon
+++ b/phpstan.dist.neon
@@ -2,9 +2,11 @@ includes:
     - phpstan-baseline.neon
 
 parameters:
-    level: 1
+    level: 5
     paths:
         - src
         - ajax
+    excludePaths:
+        - src/QUI/Menu/Rest/Provider.php
     bootstrapFiles:
         - tests/phpstan-bootstrap.php
\ No newline at end of file
diff --git a/phpunit.dist.xml b/phpunit.dist.xml
new file mode 100644
index 0000000000000000000000000000000000000000..f6c7becf0c12757beb871a9333e2d81e02aa7cae
--- /dev/null
+++ b/phpunit.dist.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit bootstrap="tests/phpunit-bootstrap.php">
+    <testsuites>
+        <testsuite name="Tests">
+            <directory>tests/</directory>
+        </testsuite>
+    </testsuites>
+</phpunit>
diff --git a/settings.xml b/settings.xml
index 0e36dcfeb3e0e116c2af29efcfc736c87d8094f6..0fbaf35edca6debc32e812160eb7056c77cffe78 100644
--- a/settings.xml
+++ b/settings.xml
@@ -31,7 +31,7 @@
                         <type><![CDATA[number]]></type>
                         <defaultvalue>1</defaultvalue>
                     </conf>
-                </section>`
+                </section>
                 <section name="mobileMenu.slideoutAdvanced.settings">
                     <conf name="homeLink">
                         <type><![CDATA[number]]></type>
@@ -41,6 +41,19 @@
                         <type><![CDATA[number]]></type>
                     </conf>
                 </section>
+                <section name="floatingMenu.settings">
+                    <conf name="menuId">
+                        <type><![CDATA[number]]></type>
+                    </conf>
+                    <conf name="showLangSwitch">
+                        <type><![CDATA[bool]]></type>
+                        <defaultvalue>false</defaultvalue>
+                    </conf>
+                    <conf name="toggleButton">
+                        <type><![CDATA[text]]></type>
+                        <defaultvalue>mobile</defaultvalue>
+                    </conf>
+                </section>
             </config>
 
             <window>
@@ -176,6 +189,56 @@
                                 </description>
                             </input>
                         </settings>
+
+                        <!-- floated menu -->
+                        <settings title="floatingMenu.settings" name="floatingMenu.settings">
+                            <title>
+                                <locale group="quiqqer/menu" var="floatingMenu.settings.title"/>
+                            </title>
+
+                            <text>
+                                <locale group="quiqqer/menu" var="floatingMenu.settings.desc"/>
+                            </text>
+
+                            <input conf="floatingMenu.settings.menuId" type="text"
+                                   data-qui="package/quiqqer/menu/bin/Controls/Independent/Input"
+                            >
+                                <text>
+                                    <locale group="quiqqer/menu" var="floatingMenu.settings.menuId"/>
+                                </text>
+                                <description>
+                                    <locale group="quiqqer/menu" var="floatingMenu.settings.menuId.desc"/>
+                                </description>
+                            </input>
+
+                            <input conf="floatingMenu.settings.showLangSwitch" type="checkbox">
+                                <text>
+                                    <locale group="quiqqer/menu" var="floatingMenu.settings.showLangSwitch"/>
+                                </text>
+                                <description>
+                                    <locale group="quiqqer/menu" var="floatingMenu.settings.showLangSwitch.desc"/>
+                                </description>
+                            </input>
+
+<!--                            <select conf="floatingMenu.settings.toggleButton">-->
+<!--                                <text>-->
+<!--                                    <locale group="quiqqer/menu" var="floatingMenu.settings.toggleButton"/>-->
+<!--                                </text>-->
+<!--                                <description>-->
+<!--                                    <locale group="quiqqer/menu" var="floatingMenu.settings.toggleButton.desc"/>-->
+<!--                                </description>-->
+
+<!--                                <option value="mobile">-->
+<!--                                    <locale group="quiqqer/menu" var="floatingMenu.settings.toggleButton.mobile"/>-->
+<!--                                </option>-->
+<!--                                <option value="always">-->
+<!--                                    <locale group="quiqqer/menu" var="floatingMenu.settings.toggleButton.always"/>-->
+<!--                                </option>-->
+<!--                                <option value="hide">-->
+<!--                                    <locale group="quiqqer/menu" var="floatingMenu.settings.toggleButton.hide"/>-->
+<!--                                </option>-->
+<!--                            </select>-->
+                        </settings>
                     </category>
                 </categories>
 
diff --git a/src/QUI/Menu/EventHandler.php b/src/QUI/Menu/EventHandler.php
index cf6ae9c3c928901e6725eb86984185225e1d9d28..7312604803d807bed6427f536f8efe86ad142691 100644
--- a/src/QUI/Menu/EventHandler.php
+++ b/src/QUI/Menu/EventHandler.php
@@ -3,9 +3,11 @@
 namespace QUI\Menu;
 
 use QUI;
+use QUI\Config;
 use QUI\Interfaces\Projects\Site;
 use Smarty;
 use SmartyException;
+use QUI\Smarty\Collector;
 
 /**
  * Class EventHandler
@@ -14,6 +16,11 @@
  */
 class EventHandler
 {
+    /**
+     * Cache Manager Configs
+     */
+    public static ?Config $Config = null;
+
     /**
      * @return string
      */
@@ -76,4 +83,32 @@ public static function onQuiqqerMenuIndependentClear($menuId): void
     {
         QUI\Cache\Manager::clear(self::menuCacheName());
     }
+
+    /**
+     * @param Collector $Collector
+     * @param QUI\Template $Template
+     * @return void
+     */
+    public static function onQuiqqerTemplateBodyEnd(Collector $Collector, QUI\Template $Template): void
+    {
+        $Project = $Template->getAttribute('Project');
+        $menuId = intval($Project->getConfig('floatingMenu.settings.menuId'));
+
+        if (!$menuId) {
+            return;
+        }
+
+        $showLandSwitch = $Project->getConfig('floatingMenu.settings.showLangSwitch');
+        $toggleButton = $Project->getConfig('floatingMenu.settings.toggleButton');
+        // todo - showToggleButton works not correctly in FloatedNav
+        $toggleButton = 'mobile';
+
+        $FloatingMenu = new QUI\Menu\Controls\FloatedNav([
+            'menuId' => $menuId,
+            'showLangSwitch' => $showLandSwitch,
+            'showToggleButton' => $toggleButton
+        ]);
+
+        $Collector->append($FloatingMenu->create());
+    }
 }
diff --git a/tests/phpunit-bootstrap.php b/tests/phpunit-bootstrap.php
new file mode 100644
index 0000000000000000000000000000000000000000..eca92fd67bed8ae4ec424ed82d300119d792f042
--- /dev/null
+++ b/tests/phpunit-bootstrap.php
@@ -0,0 +1,11 @@
+<?php
+
+if (!defined('QUIQQER_SYSTEM')) {
+    define('QUIQQER_SYSTEM', true);
+}
+
+if (!defined('QUIQQER_AJAX')) {
+    define('QUIQQER_AJAX', true);
+}
+
+require_once __DIR__ . '/../../../../bootstrap.php';