diff --git a/Readme.md b/Readme.md
index cd2e6f577348acd11b1b3b40ec862b401ea1fdd3..1621bf71122201dc6b3bc44fb04fcee43a12184f 100644
--- a/Readme.md
+++ b/Readme.md
@@ -16,8 +16,17 @@ We used the WAYF script in the login page so far.
 When editing login pages in Ilias 9 then javascript is now filtered out while saving. That's why we couldn't embed our current WAYF script anymore.
 This is a standalone login page that offers a list of organisations and can be customized.
 
+This reads the discofeed. By a given positive list of Idps it shows a list of identity providers to login on the visitors organisation.
+
 ![Login screen with mode "boxes"](docs/images/login_mode_boxes.png)
 
+## Features
+
+* Displays a list of allowed Identity providers
+* The list is filtered during typing in the search field. When entering multiple words all keywords must match.
+* The filter is stored in localstorage of the webbrowser. On reload or the next week the last filter will be activated.
+* A set of filter buttons by TLD will be generated. It is shown when minimum 2 TLDs were found.
+
 ## Requirements
 
 * PHP 8.2
diff --git a/classes/shibd_discofeed.class.php b/classes/shibd_discofeed.class.php
index 845846249692da2e06a612a2b2c6b96011074e56..a982228ce66278172e2c8d62499e2cfed8a0344c 100644
--- a/classes/shibd_discofeed.class.php
+++ b/classes/shibd_discofeed.class.php
@@ -1,17 +1,54 @@
 <?php
-
+/**
+ * ======================================================================
+ * 
+ * AAI LOGIN WITH SHIBBOLETH HANDLING MULTIPLE ORGANIZATIONS
+ * 
+ * included functions
+ * License: GNU GPL 3.0
+ * Source: https://git-repo.iml.unibe.ch/iml-open-source/login-aai
+ * ======================================================================
+ */
 class shibd_discofeed
 {
-    protected string $url_list = '/Shibboleth.sso/DiscoFeed';
-    protected string $url_login = '/Shibboleth.sso/Login';
+    /**
+     * Url to the discofeed that returns a json with al idps
+     * @var string
+     */
+    protected string $_sDiscofeedUrl = '/Shibboleth.sso/DiscoFeed';
+
+    /**
+     * Url to generate a static Shibboleth login url
+     * @var string
+     */
+    protected string $_sShibLoginUrl = '/Shibboleth.sso/Login';
 
+    /**
+     * Language to search for in the discofeed; a 2 letter code
+     * @var string
+     */
     protected string $lang = 'en';
 
     // caching of discofeed
+
+    /**
+     * Filename of the cache file for the Shibboleth discofeed
+     * @var string
+     */
     protected string $_sCachefile = 'discofeed.json';
+
+    /**
+     * Caching time for the discofeed cache in seconds
+     * @var int
+     */
     protected int $_iCacheTtl;
 
-    protected array $aConfig = [];
+    /**
+     * Self base URL of the current app tu build Shibboleth links
+     * @var string
+     */
+    protected string $SELFURL = '';
+    // protected array $aConfig = [];
 
     // ----------------------------------------------------------------------
 
@@ -33,9 +70,10 @@ class shibd_discofeed
         if(!$SELFURL) {
             die("ERROR: SELFURL is not set. \$_SERVER['SERVER_NAME'] is not available.");
         }
+        $this->SELFURL = $SELFURL;
 
-        $this->url_list = "$SELFURL$this->url_list";
-        $this->url_login = "$SELFURL$this->url_login";
+        $this->_sDiscofeedUrl = "$SELFURL$this->_sDiscofeedUrl";
+        $this->_sShibLoginUrl = "$SELFURL$this->_sShibLoginUrl";
 
         $this->_sCachefile = dirname(__DIR__).'/'
             .($aConfig['cachefile']??'discofeed.json')
@@ -56,7 +94,7 @@ class shibd_discofeed
     {
         if (!file_exists($this->_sCachefile) || filemtime($this->_sCachefile) < time() - $this->_iCacheTtl) {
             // echo "DEBUG: IDP - reading from Shibboleth<br>";
-            $aReturn = json_decode(file_get_contents($this->url_list), 1);
+            $aReturn = json_decode(file_get_contents($this->_sDiscofeedUrl), 1);
     
             if ($aReturn && is_array($aReturn)) {
                 // echo "DEBUG: IDP - storing cache<br>";
@@ -100,13 +138,15 @@ class shibd_discofeed
 
                     $sImage = $aEntry['Logos'][1]['value'] ?? ($aEntry['Logos'][0]['value'] ?? '');
     
-                    $sUrl = $this->url_login
+                    // see also https://help.switch.ch/aai/guides/discovery/login-link-composer/
+                    $sUrl = $this->_sShibLoginUrl
                         . '?entityID='. urlencode($sEntityId) 
                         . "&target=" . urlencode($sEntityId) 
                         . "&target=" . urlencode($this->SELFURL.($aConfig['return-url']??''))
                         ;
     
-                    $aReturn[] = array_merge([
+                    $sKey=$sLabel;
+                    $aReturn[$sKey] = array_merge([
                         '_label' => $sLabel,
                         '_description' => $sDescription,
                         '_keywords' => $sKeywords,
@@ -120,6 +160,7 @@ class shibd_discofeed
                 }
             }
         }
-        return $aReturn;
+        ksort($aReturn);
+        return array_values($aReturn);
     }
 }
\ No newline at end of file
diff --git a/docs/images/login_mode_boxes.png b/docs/images/login_mode_boxes.png
index 23ed0c93ec671500d961630e18b0b1790359ad0c..cf93cfe48b6ae2e08742a0e9d10d3da11a6cf6c6 100644
Binary files a/docs/images/login_mode_boxes.png and b/docs/images/login_mode_boxes.png differ
diff --git a/functions.js b/functions.js
index a10c998373670e2818081cda834abc482cc7196c..5740f00b44fbbce18b8208582da7efc9d55ec3f1 100644
--- a/functions.js
+++ b/functions.js
@@ -24,33 +24,62 @@ var sLsvar = 'aailogin-q';
 /**
  * Apply filter and reduce listed Idps
  */
-function applyfilter(){
+function applyfilter() {
 
-    if(oFilter){
+    if (oFilter) {
         var q = document.getElementById('filter').value;
+        var aQ = q.split(" ");
 
-        for(var i=0; i<document.getElementsByClassName('idp').length; i++){
+        // reduce boxes based on filter using AND condition
+        for (var i = 0; i < document.getElementsByClassName('idp').length; i++) {
             var idp = document.getElementsByClassName('idp')[i];
 
-            var bShow=(q==""
-                ? true
-                  // strip innerHTML and compare lowercase of it with lowercase of query
-                : idp.innerHTML.replace(/<[^>]*>/g, "").toLowerCase().indexOf(q.toLowerCase())>=0
-            );
-                
+            var bShow = true;
+            if (q != "") {
+                var sText = idp.innerHTML.replace(/<[^>]*>/g, "");
+                for (var iPart = 0; iPart < aQ.length; iPart++) {
+                    var qPart = aQ[iPart];
+                    bShow = bShow & sText.toLowerCase().indexOf(qPart.toLowerCase()) >= 0;
+
+                }
+            }
             idp.className = bShow ? 'idp' : 'idp hide';
-            document.getElementById('resetfilter').style.display = (q>"") ? 'inline': 'none';
+            document.getElementById('resetfilter').style.display = (q > "") ? 'inline' : 'none';
+        }
+
+        var aBtns = document.getElementsByClassName('filterbutton');
+        for (var i = 0; i < aBtns.length; i++) {
+            var bMarked = false;
+            for (var iPart = 0; iPart < aQ.length; iPart++) {
+                var qPart = aQ[iPart];
+                var id2search = 'filterbtn-dot-' + qPart.replace(/^./, '');
+                var qPart = aQ[iPart];
+                if(aBtns[i].id == id2search){
+                    bMarked = true;
+                    break;
+                }
+            }
+            aBtns[i].className = bMarked ? 'filterbutton active' : 'filterbutton';
         }
-        localStorage.setItem(sLsvar,q);
+
+        document.getElementById('filter').focus();
+        localStorage.setItem(sLsvar, q);
     };
 }
 
-function setFilter(sNewFiltervalue){
-    document.getElementById('filter').value=sNewFiltervalue;
-    applyfilter();    
+/**
+ * Set a new filter value or reset it
+ * @param {string} sNewFiltervalue New value to write into the filter field
+ */
+function setFilter(sNewFiltervalue) {
+    document.getElementById('filter').value = sNewFiltervalue;
+    applyfilter();
 }
 
-function resetFilter(){
+/**
+ * Reset the filter and show all Items
+ */
+function resetFilter() {
     setFilter("");
 }
 /**
@@ -58,39 +87,24 @@ function resetFilter(){
  * It renders an input field, reads the last search value from local storage 
  * and apllies this filter
  */
-function showFilterBox(){
-    if(oFilter){
-        var q=localStorage.getItem(sLsvar);
-        if(!q){
-            q='';
+function showFilterBox() {
+    if (oFilter) {
+        var q = localStorage.getItem(sLsvar);
+        if (!q) {
+            q = '';
         }
 
-        var btnList = '';
-
-        var aTlds = [];
-        for(var i=0; i<document.getElementsByClassName('idp').length; i++){
-            var idp = document.getElementsByClassName('idp')[i];
-            var sText=idp.innerHTML.replace(/<[^>]*>/g, "").split('.').pop().trim().toUpperCase();
-            if (sText){
-                aTlds[sText]=1;
-            }
-        }
-        // if(aTlds.length){            
-            for(var tld in aTlds){
-                btnList+=' <a href="#" class="filterbutton" onclick="setFilter(\' .'+tld+'\'); return false;">.'+tld+'</a> ';
-            }
-        // }
 
         oFilter.style.display = 'block';
 
-        oFilter.innerHTML='<input type="text" id="filter" placeholder="" onchange="applyfilter()" onkeydown="applyfilter()" onkeyup="applyfilter()" value="'+q+'"/>'
-            +'<a id="resetfilter" onclick="resetFilter(); return false;"> X </a><br>'
-            + (btnList ? '<br>'+btnList : '' )
+        oFilter.innerHTML = '<input type="text" id="filter" placeholder="" onchange="applyfilter()" onkeydown="applyfilter()" onkeyup="applyfilter()" value=""/>'
+            + '<a id="resetfilter" onclick="resetFilter(); return false;"> X </a><br>'
             ;
-        applyfilter();
-        for(var i=0; i<document.getElementsByClassName('idp').length; i++){
+        setFilter(q);
+
+        for (var i = 0; i < document.getElementsByClassName('idp').length; i++) {
             var idp = document.getElementsByClassName('idp')[i];
-            idp.setAttribute("onclick","localStorage.setItem(sLsvar,this.innerText);");
+            idp.setAttribute("onclick", "localStorage.setItem(sLsvar,this.innerText);");
         }
     };
 };
diff --git a/inc_functions.php b/inc_functions.php
index 3ce963d45c38a09dc8a1f856cd208aa10ba22369..0152efb0c39abaf9897e48f590422237d520ab3f 100644
--- a/inc_functions.php
+++ b/inc_functions.php
@@ -16,7 +16,6 @@
 
 require 'classes/shibd_discofeed.class.php';
 
-
 // get the user config
 if (!file_exists('config.php')) {
     die("ERROR: file config.php does not exist yet.");
@@ -35,6 +34,7 @@ $aIdplist = $oDiscofeed->getIdps();
 
 /**
  * Show a info or error message
+ * 
  * @param string $sLevel   level: one of "info", "error"
  * @param string $sMessage Message to show
  * @return void
@@ -47,6 +47,8 @@ function showMessage(string $sLevel, string $sMessage)
 
 /**
  * Get a list of static links for browsers without javascript
+ * see also https://help.switch.ch/aai/guides/discovery/login-link-composer/
+ * 
  * @param array $aIdplist
  * @return string
  */
@@ -59,3 +61,4 @@ function getStaticlinks($aIdplist){
 
 }
 
+// ----------------------------------------------------------------------
diff --git a/inc_mode_boxes.php b/inc_mode_boxes.php
index 0cf2051d675eb6e917dc7ed6424fef2db331a048..32ee556a2afad4e114590d1d29373b4eb2dfbab6 100644
--- a/inc_mode_boxes.php
+++ b/inc_mode_boxes.php
@@ -11,22 +11,41 @@
 
 if (is_array($aIdplist) && count($aIdplist)) {
     $sOut = '';
-    $sOut .= '<div id="filterbox"></div><div class="boxes">';
+
+    $sDomainFilter = '';
+    $aTld = [];
+
     foreach ($aIdplist as $aEntry) {
+        $aTld[$aEntry['_tld']] = 1;
         $sOut .= '
                     <a href="' . $aEntry['_url']. '" 
                         class="idp"
-                        title="' . str_replace('"', '',$aEntry['_description']) . '"
-                        >' . htmlentities($aEntry['_label']) . '<br>
-                        <span class="hidden">' . str_replace('"', '', $aEntry['_description'] . ' ' . $aEntry['_keywords']) . ' .'. $aEntry['_tld'] . '</span>
+                        title="' . strip_tags($aEntry['_description']) . '"
+                        >
+                        <span>' . htmlentities($aEntry['_label']) . '</span><br>
+                        <span class="hidden"> .'. $aEntry['_tld'] . '</span>
                         <img src="' . $aEntry['_image'] . '"><br>
                     </a>
                 '
-                . "\n"
                 ;
     }
-    $sOut.='</div><div style="clear:both"></div>';
-    echo $sOut;
+    
+    // Generate a list of TLDs to filter by it.
+    if(count($aTld)>1) {
+        ksort($aTld);
+        foreach(array_keys($aTld) as $sTld) {
+            $sId='filterbtn-dot-'.str_replace('.', '', $sTld);
+            $sDomainFilter.='<a href="#" id="'.$sId.'" class="filterbutton" onclick="setFilter(\'.'.$sTld.' \'); return false;">.'.$sTld.'</a> ';
+        }
+        $sDomainFilter = '<div id="filterByDomain">' . $sDomainFilter . '</div>';
+    }
+
+    echo '<div id="filterbox"></div>'
+        . $sDomainFilter
+        .'<div class="boxes">
+            '.$sOut.'
+            </div><div style="clear:both"></div>';
+    
 } else {
     echo '<div class="msg error">No IDPs found in Discovery Feed.</div>';
 }
diff --git a/index.php b/index.php
index 50463e30d9cd836b80e7d8cb331735ba4b136abe..ae5b490ab73697d220b536b9aa8b2c372f138b0c 100644
--- a/index.php
+++ b/index.php
@@ -59,7 +59,7 @@
     </div>
     <script type="text/javascript" defer src="functions.js"></script>
 
-    <footer><?php echo $aConfig['title'] ?? 'AAI Login'; ?><br>AAI Login v0.2 - <a href="https://git-repo.iml.unibe.ch/iml-open-source/login-aai">Source</a></a></footer>
+    <footer><strong><?php echo $aConfig['title'] ?? 'AAI Login'; ?></strong><br>AAI Login v0.2 - <a href="https://git-repo.iml.unibe.ch/iml-open-source/login-aai">Source</a></a></footer>
 </body>
 
 </html>
\ No newline at end of file
diff --git a/login_aai.css b/login_aai.css
index 5dfc3452daf754a0f45cdd7863265a224ab42bdd..101f392321569716b9069018791a78ac888ff0b5 100644
--- a/login_aai.css
+++ b/login_aai.css
@@ -28,8 +28,8 @@
     --inputfilter-border: 2px solid #eee;
     --inputfilter-color:#789;
 
-    --btn-border: 2px solid #ddd;
-    --btn-bg:#eee;
+    --btn-border: 2px solid #eee;
+    --btn-bg:#f8f8f8;
     --btn-color: #888;
 
     --resetfilter-border: 2px solid #dcc;
@@ -53,9 +53,11 @@ body {
 
 h1{color:var(--h1-color);}
 h2{color: var(--h2-color);}
+div#head, div.content, footer {
+    width: 70%;
+}
 div#head {
     margin: 1em auto 1em;
-    width: 60%;
 }
 div.content {
     background: var(--content-bg);
@@ -64,7 +66,6 @@ div.content {
     box-shadow: var(--content-shadow);
     margin: 2em auto 2em;
     padding: 2em;
-    width: 60%;
 }
 div.msg{
     border-radius: 1em;
@@ -83,7 +84,6 @@ div.info{
 }
 
 footer{
-    width: 60%;
     color: var(--footer-color);
     margin: 10em auto 5em;
     text-align: right; 
@@ -94,7 +94,12 @@ footer{
 div#filterbox{
     margin-bottom: 1em;
 }
-div#filterbox a.filterbutton, a#resetfilter{
+#filterByDomain{
+    margin-top: 3em;
+    margin-bottom: 3em;
+}
+
+a.filterbutton, a#resetfilter{
     background: var(--btn-bg);
     border: var(--btn-border);
     border-radius: 0.2em;
@@ -105,12 +110,16 @@ div#filterbox a.filterbutton, a#resetfilter{
     padding: 0.5em;
     text-decoration: none;
 }
+a.filterbutton.active{
+    background: #fff;
+    border-top-color: #68a;
+}
 input#filter{
     border: var(--inputfilter-border);
     padding: 0.5em;
     color:var(--inputfilter-color);
     font-size: 130%;
-    width: 20em;
+    width: 70%;
 }
 a#resetfilter{
     background:var(--resetfilter-bg);
@@ -141,9 +150,9 @@ div.boxes a.idp{
     text-align: center;
     text-decoration: none;
     transition: all 0.1s ease-in-out;
-    width: 10em;
+    width: 14em;
 }
-div.boxes .idp img { height: 80px;}
+div.boxes .idp img { height: 48px;}
 div.boxes a.idp:hover{ 
     box-shadow: var(--idp-hover-shadow);
 }