diff --git a/modules/admin/lib/FederationController.php b/modules/admin/lib/FederationController.php
new file mode 100644
index 0000000000000000000000000000000000000000..a6608ac62789fe11ce39c0cb03a108db8d152964
--- /dev/null
+++ b/modules/admin/lib/FederationController.php
@@ -0,0 +1,374 @@
+<?php
+
+namespace SimpleSAML\Module\admin;
+
+use SimpleSAML\Locale\Translate;
+use SimpleSAML\Metadata\MetaDataStorageHandler;
+use SimpleSAML\Metadata\SAMLBuilder;
+use SimpleSAML\Module;
+use SimpleSAML\Module\adfs\IdP\ADFS as ADFS_IdP;
+use SimpleSAML\Module\saml\IdP\SAML1 as SAML1_IdP;
+use SimpleSAML\Module\saml\IdP\SAML2 as SAML2_IdP;
+use SimpleSAML\Utils\Auth;
+
+/**
+ * Controller class for the admin module.
+ *
+ * This class serves the federation views available in the module.
+ *
+ * @package SimpleSAML\Module\admin
+ */
+class FederationController
+{
+
+    /** @var \SimpleSAML\Configuration */
+    protected $config;
+
+    /** @var MetaDataStorageHandler */
+    protected $mdHandler;
+
+    /** @var Menu */
+    protected $menu;
+
+
+    /**
+     * FederationController constructor.
+     *
+     * @param \SimpleSAML\Configuration $config The configuration to use.
+     */
+    public function __construct(\SimpleSAML\Configuration $config)
+    {
+        $this->config = $config;
+        $this->menu = new Menu();
+        $this->mdHandler = MetaDataStorageHandler::getMetadataHandler();
+    }
+
+
+    /**
+     * Display the federation page.
+     *
+     * @return \SimpleSAML\XHTML\Template
+     * @throws \SimpleSAML\Error\Exception
+     * @throws \SimpleSAML_Error_Exception
+     */
+    public function main()
+    {
+        Auth::requireAdmin();
+
+        // initialize basic metadata array
+        $hostedSPs = $this->getHostedSP();
+        $hostedIdPs = $this->getHostedIdP();
+        $entries = [
+            'hosted' => array_merge($hostedSPs, $hostedIdPs),
+            'remote' => [
+                'saml20-idp-remote' => !empty($hostedSPs) ? $this->mdHandler->getList('saml20-idp-remote') : [],
+                'shib13-idp-remote' => !empty($hostedSPs) ? $this->mdHandler->getList('shib13-idp-remote') : [],
+                'saml20-sp-remote' => $this->config->getBoolean('enable.saml20-idp', false) === true
+                    ? $this->mdHandler->getList('saml20-sp-remote') : [],
+                'shib13-sp-remote' => $this->config->getBoolean('enable.shib13-idp', false) === true
+                    ? $this->mdHandler->getList('shib13-sp-remote') : [],
+                'adfs-sp-remote' => ($this->config->getBoolean('enable.adfs-idp', false) === true) &&
+                    Module::isModuleEnabled('adfs') ? $this->mdHandler->getList('adfs-sp-remote') : [],
+            ],
+        ];
+
+        // initialize template and language
+        $t = new \SimpleSAML\XHTML\Template($this->config, 'admin:federation.twig');
+        $language = $t->getTranslator()->getLanguage()->getLanguage();
+        $defaultLang = $this->config->getString('language.default', 'en');
+
+        // process hosted entities
+        foreach ($entries['hosted'] as $index => $entity) {
+            if (isset($entity['name']) && is_string($entity['name'])) {
+                // if the entity has no internationalized name, fake it
+                $entries['hosted'][$index]['name'] = [$language => $entity['name']];
+            }
+        }
+
+        // clean up empty remote entries
+        foreach ($entries['remote'] as $key => $value) {
+            if (empty($value)) {
+                unset($entries['remote'][$key]);
+            }
+        }
+
+        $translators = [
+            'name' => 'name_translated',
+            'descr' => 'descr_translated',
+            'OrganizationDisplayName' => 'organizationdisplayname_translated',
+        ];
+
+        foreach ($entries['remote'] as $key => $set) {
+            foreach ($set as $entityid => $entity) {
+                foreach ($translators as $old => $new) {
+                    if (isset($entity[$old][$language])) {
+                        $entries['remote'][$key][$entityid][$new] = $entity[$old][$language];
+                    } elseif (isset($entity[$old][$defaultLang])) {
+                        $entries['remote'][$key][$entityid][$new] = $entity[$old][$defaultLang];
+                    } elseif (isset($entity[$old]['en'])) {
+                        $entries['remote'][$key][$entityid][$new] = $entity[$old]['en'];
+                    } elseif (isset($entries['remote'][$key][$entityid][$old])) {
+                        $entries['remote'][$key][$entityid][$new] = $entries['remote'][$key][$entityid][$old];
+                    }
+                }
+            }
+        }
+
+        $t->data = [
+            'links' => [
+                [
+                    'href' => Module::getModuleURL('admin/metadata-converter'),
+                    'text' => Translate::noop('XML to SimpleSAMLphp metadata converter'),
+                ]
+            ],
+            'entries' => $entries,
+            'mdtype' => [
+                'saml20-sp-remote' => Translate::noop('SAML 2.0 SP metadata'),
+                'saml20-sp-hosted' => Translate::noop('SAML 2.0 SP metadata'),
+                'saml20-idp-remote' => Translate::noop('SAML 2.0 IdP metadata'),
+                'saml20-idp-hosted' => Translate::noop('SAML 2.0 IdP metadata'),
+                'shib13-sp-remote' => Translate::noop('SAML 1.1 SP metadata'),
+                'shib13-sp-hosted' => Translate::noop('SAML 1.1 SP metadata'),
+                'shib13-idp-remote' => Translate::noop('SAML 1.1 IdP metadata'),
+                'shib13-idp-hosted' => Translate::noop('SAML 1.1 IdP metadata'),
+                'adfs-sp-remote' => Translate::noop('ADFS SP metadata'),
+                'adfs-sp-hosted' => Translate::noop('ADFS SP metadata'),
+                'adfs-idp-remote' => Translate::noop('ADFS IdP metadata'),
+                'adfs-idp-hosted' => Translate::noop('ADFS IdP metadata'),
+            ],
+            'logouturl' => Auth::getAdminLogoutURL(),
+        ];
+
+        Module::callHooks('federationpage', $t);
+        $this->menu->addOption('logout', $t->data['logouturl'], Translate::noop('Log out'));
+        return $this->menu->insert($t);
+    }
+
+
+    /**
+     * Get a list of the hosted IdP entities, including SAML 2, SAML 1.1 and ADFS.
+     *
+     * @return array
+     * @throws \Exception
+     */
+    private function getHostedIdP()
+    {
+        $entities = [];
+
+        // SAML 2
+        if ($this->config->getBoolean('enable.saml20-idp', false)) {
+            try {
+                $idps = $this->mdHandler->getList('saml20-idp-hosted');
+                $saml2entities = [];
+                if (count($idps) > 1) {
+                    foreach ($idps as $index => $idp) {
+                        $idp['url'] = Module::getModuleURL('saml/2/idp/metadata/'.$idp['auth']);
+                        $idp['metadata-set'] = 'saml20-idp-hosted';
+                        $idp['metadata-index'] = $index;
+                        $idp['metadata_array'] = SAML2_IdP::getHostedMetadata($idp['entityid']);
+                        $saml2entities[] = $idp;
+                    }
+                } else {
+                    $saml2entities['saml20-idp'] = $this->mdHandler->getMetaDataCurrent('saml20-idp-hosted');
+                    $saml2entities['saml20-idp']['url'] = \SimpleSAML\Utils\HTTP::getBaseURL().'saml2/idp/metadata.php';
+                    $saml2entities['saml20-idp']['metadata_array'] =
+                        SAML2_IdP::getHostedMetadata(
+                            $this->mdHandler->getMetaDataCurrentEntityID('saml20-idp-hosted')
+                        );
+                }
+
+                foreach ($saml2entities as $index => $entity) {
+                    $builder = new SAMLBuilder($entity['entityid']);
+                    $builder->addMetadataIdP20($entity['metadata_array']);
+                    $builder->addOrganizationInfo($entity['metadata_array']);
+                    foreach ($entity['metadata_array']['contacts'] as $contact) {
+                        $builder->addContact($contact['contactType'], $contact);
+                    }
+
+                    $entity['metadata'] = \SimpleSAML\Metadata\Signer::sign(
+                        $builder->getEntityDescriptorText(),
+                        $entity['metadata_array'],
+                        'SAML 2 IdP'
+                    );
+                    $entities[$index] = $entity;
+                }
+            } catch (\Exception $e) {
+                \SimpleSAML\Logger::error('Federation: Error loading saml20-idp: '.$e->getMessage());
+            }
+        }
+
+        // SAML 1.1 / Shib13
+        if ($this->config->getBoolean('enable.shib13-idp', false)) {
+            try {
+                $idps = $this->mdHandler->getList('shib13-idp-hosted');
+                $shib13entities = [];
+                if (count($idps) > 1) {
+                    foreach ($idps as $index => $idp) {
+                        $idp['url'] = Module::getModuleURL('saml/1.1/idp/metadata/'.$idp['auth']);
+                        $idp['metadata-set'] = 'shib13-idp-hosted';
+                        $idp['metadata-index'] = $index;
+                        $idp['metadata_array'] = SAML1_IdP::getHostedMetadata($idp['entityid']);
+                        $shib13entities[] = $idp;
+                    }
+                } else {
+                    $shib13entities['shib13-idp'] = $this->mdHandler->getMetaDataCurrent('shib13-idp-hosted');
+                    $shib13entities['shib13-idp']['url'] = \SimpleSAML\Utils\HTTP::getBaseURL().
+                        'shib13/idp/metadata.php';
+                    $shib13entities['shib13-idp']['metadata_array'] =
+                        SAML1_IdP::getHostedMetadata(
+                            $this->mdHandler->getMetaDataCurrentEntityID('shib13-idp-hosted')
+                        );
+                }
+                
+                foreach ($shib13entities as $index => $entity) {
+                    $builder = new SAMLBuilder($entity['entityid']);
+                    $builder->addMetadataIdP11($entity['metadata_array']);
+                    $builder->addOrganizationInfo($entity['metadata_array']);
+                    foreach ($entity['metadata_array']['contacts'] as $contact) {
+                        $builder->addContact($contact['contactType'], $contact);
+                    }
+
+                    $entity['metadata'] = \SimpleSAML\Metadata\Signer::sign(
+                        $builder->getEntityDescriptorText(),
+                        $entity['metadata_array'],
+                        'SAML 2 SP'
+                    );
+                    $entities[$index] = $entity;
+                }
+            } catch (\Exception $e) {
+                \SimpleSAML\Logger::error('Federation: Error loading shib13-idp: '.$e->getMessage());
+            }
+        }
+
+        // ADFS
+        if ($this->config->getBoolean('enable.adfs-idp', false) && Module::isModuleEnabled('adfs')) {
+            try {
+                $idps = $this->mdHandler->getList('adfs-idp-hosted');
+                $adfsentities = [];
+                if (count($idps) > 1) {
+                    foreach ($idps as $index => $idp) {
+                        $idp['url'] = Module::getModuleURL('adfs/idp/metadata/'.$idp['auth']);
+                        $idp['metadata-set'] = 'adfs-idp-hosted';
+                        $idp['metadata-index'] = $index;
+                        $idp['metadata_array'] = ADFS_IdP::getHostedMetadata($idp['entityid']);
+                        $adfsentities[] = $idp;
+                    }
+                } else {
+                    $adfsentities['adfs-idp'] = $this->mdHandler->getMetaDataCurrent('adfs-idp-hosted');
+                    $adfsentities['adfs-idp']['url'] = Module::getModuleURL('adfs/idp/metadata.php');
+                    $adfsentities['adfs-idp']['metadata_array'] =
+                        ADFS_IdP::getHostedMetadata(
+                            $this->mdHandler->getMetaDataCurrentEntityID('adfs-idp-hosted')
+                        );
+                }
+
+                foreach ($adfsentities as $index => $entity) {
+                    $builder = new SAMLBuilder($entity['entityid']);
+                    $builder->addSecurityTokenServiceType($entity['metadata_array']);
+                    $builder->addOrganizationInfo($entity['metadata_array']);
+                    foreach ($entity['metadata_array']['contacts'] as $contact) {
+                        $builder->addContact($contact['contactType'], $contact);
+                    }
+
+                    $entity['metadata'] = \SimpleSAML\Metadata\Signer::sign(
+                        $builder->getEntityDescriptorText(),
+                        $entity['metadata_array'],
+                        'ADFS IdP'
+                    );
+                    $entities[$index] = $entity;
+                }
+            } catch (\Exception $e) {
+                \SimpleSAML\Logger::error('Federation: Error loading adfs-idp: '.$e->getMessage());
+            }
+        }
+
+        // process certificate information and dump the metadata array
+        foreach ($entities as $index => $entity) {
+            $entities[$index]['type'] = $entity['metadata-set'];
+            foreach ($entity['metadata_array']['keys'] as $kidx => $key) {
+                $key['url'] = Module::getModuleURL(
+                    'admin/cert',
+                    [
+                        'set' => $entity['metadata-set'],
+                        'idp' => $entity['metadata-index'],
+                        'prefix' => $key['prefix'],
+                    ]
+                );
+                $key['name'] = 'idp';
+                unset($entity['metadata_array']['keys'][$kidx]['prefix']);
+                $entities[$index]['certificates'][] = $key;
+            }
+
+            // only one key, reduce
+            if (count($entity['metadata_array']['keys']) === 1) {
+                $cert = array_pop($entity['metadata_array']['keys']);
+                $entity['metadata_array']['certData'] = $cert['X509Certificate'];
+                unset($entity['metadata_array']['keys']);
+            }
+
+            $entities[$index]['metadata_array'] = var_export($entity['metadata_array'], true);
+        }
+
+        return $entities;
+    }
+
+
+    /**
+     * Get an array of entities describing the local SP instances.
+     *
+     * @return array
+     * @throws \SimpleSAML\Error\Exception If OrganizationName is set for an SP instance but OrganizationURL is not.
+     */
+    private function getHostedSP()
+    {
+        $entities = [];
+
+        /** @var \SimpleSAML\Module\saml\Auth\Source\SP $source */
+        foreach (\SimpleSAML\Auth\Source::getSourcesOfType('saml:SP') as $source) {
+            $metadata = $source->getHostedMetadata();
+            $certificates = $metadata['keys'];
+            if (count($metadata['keys']) === 1) {
+                $cert = array_pop($metadata['keys']);
+                $metadata['certData'] = $cert['X509Certificate'];
+                unset($metadata['keys']);
+            }
+
+            // get the name
+            $name = $source->getMetadata()->getLocalizedString(
+                'name',
+                $source->getMetadata()->getLocalizedString('OrganizationDisplayName', $source->getAuthId())
+            );
+
+            $builder = new SAMLBuilder($source->getEntityId());
+            $builder->addMetadataSP20($metadata, $source->getSupportedProtocols());
+            $builder->addOrganizationInfo($metadata);
+            $xml = $builder->getEntityDescriptorText(true);
+
+            // sanitize the resulting array
+            unset($metadata['UIInfo']);
+            unset($metadata['metadata-set']);
+            unset($metadata['entityid']);
+
+            // sanitize the attributes array to remove friendly names
+            if (isset($metadata['attributes']) && is_array($metadata['attributes'])) {
+                $metadata['attributes'] = array_values($metadata['attributes']);
+            }
+
+            // sign the metadata if enabled
+            $xml = \SimpleSAML\Metadata\Signer::sign($xml, $source->getMetadata()->toArray(), 'SAML 2 SP');
+
+            $entities[] = [
+                'authid' => $source->getAuthId(),
+                'entityid' => $source->getEntityId(),
+                'type' => 'saml20-sp-hosted',
+                'url' => $source->getMetadataURL(),
+                'name' => $name,
+                'metadata' => $xml,
+                'metadata_array' => var_export($metadata, true),
+                'certificates' => $certificates,
+            ];
+        }
+
+        return $entities;
+    }
+}
diff --git a/modules/admin/templates/federation.twig b/modules/admin/templates/federation.twig
new file mode 100644
index 0000000000000000000000000000000000000000..ab2921b53e902dceeba76247091abd509ccd2580
--- /dev/null
+++ b/modules/admin/templates/federation.twig
@@ -0,0 +1,163 @@
+{% set pagetitle = 'SimpleSAMLphp installation page'|trans %}
+{% set frontpage_section = 'federation' %}
+{% extends "base.twig" %}
+
+{% block content %}
+    {%- include "@admin/includes/menu.twig" %}
+    {%- if entries.hosted is iterable %}
+
+    <h2>{% trans %}Hosted entities{% endtrans %}</h2>
+      {%- for key, set in entries.hosted %}
+        {%- set metadataset = attribute(set, 'metadata-set') %}
+        {%- if not loop.first %}
+
+        <br/>
+        {%- endif %}
+        {%- embed "includes/expander.twig" %}
+          {%- block general %}
+
+          <dl>
+            {%- if set.name %}
+
+              <dt>{{ set.name|translateFromArray }}</dt>
+            {%- endif %}
+
+            <dd>EntityID: <code>{{ set.entityid }}</code></dd>
+            {%- if set.deprecated %}
+
+              <dd><span class="entity-deprecated">Deprecated</span></dd>
+            {%- endif %}
+            {% set index = attribute(set, 'metadata-index')|default(false) %}
+            {%- if index and set.entityid != index %}
+
+              <dd>Index: <code>{{ index }}</code></dd>
+            {%- endif %}
+
+            <dd>{% trans %}Type:{% endtrans %} <strong>{{ mdtype[set.type]|trans }}</strong></dd>
+          </dl>
+          {%- endblock %}
+          {%- block content %}
+
+          <dl>
+            <dt>{% trans %}SAML Metadata{% endtrans %}</dt>
+            <dd>{% trans %}You can get the metadata XML on a dedicated URL:{% endtrans %}</dd>
+            <dd class="code-box hljs">
+              <div class="pure-button-group top-right-corner">
+                <a class="pure-button copy hljs" data-clipboard-target="#url-{{ key }}"
+                   title="{% trans %}Copy to clipboard{% endtrans %}"><span class="fa fa-copy"></span></a>
+                <a class="pure-button hljs" href="{{ set.url }}">
+                  <span class="fa fa-external-link-square"></span>
+                </a>
+              </div>
+              <code id="url-{{ key }}" class="code-box-content">{{ set.url }}</code>
+            </dd>
+            <dd>{% trans %}In SAML 2.0 Metadata XML format:{% endtrans %}</dd>
+            <dd class="code-box hljs">
+              <div class="pure-button-group top-right-corner">
+                <a class="pure-button copy hljs" data-clipboard-target="#xml-{{ key }}"
+                   title="{% trans %}Copy to clipboard{% endtrans %}"><span class="fa fa-copy"></span></a>
+              </div>
+              <div id="xml-{{ key }}" class="code-box-content xml">{{ set.metadata }}</div>
+            </dd>
+            <dt>{% trans %}SimpleSAMLphp Metadata{% endtrans %}</dt>
+            <dd>{% trans %}Use this if you are using a SimpleSAMLphp entity on
+              {#- #} the other side:{% endtrans %}</dd>
+            <dd class="code-box hljs">
+              <div class="pure-button-group top-right-corner">
+                <a class="pure-button copy hljs" data-clipboard-target="#php-{{ key }}"
+                   title="{% trans %}Copy to clipboard{% endtrans %}"><span class="fa fa-copy"></span></a>
+              </div>
+              <div id="php-{{ key }}" class="code-box-content php">
+                {#- #}$metadata['{{ set.entityid }}'] = {{ set.metadata_array }};{# -#}
+              </div>
+            </dd>
+            <dt>{% trans %}Certificates{% endtrans %}</dt>
+            {%- for cert in set.certificates %}
+              {%- if loop.first %}
+
+              <ul>
+              {%- endif %}
+
+                <li>
+                  <a href="{{ cert.url }}"><i class="fa fa-download"></i>{{ cert.name }}
+                    {#- #}{% if cert.signing %}-signing{% endif %}
+                    {#- #}{% if cert.encryption %}-encryption{% endif %}.pem
+                    {#- #}{% if cert.prefix %} ({% trans %}new{% endtrans %}){% endif %}</a>
+                </li>
+              {%- if loop.last %}
+
+              </ul>
+              {%- endif %}
+            {%- endfor %}
+
+          </dl>
+          {%- endblock %}
+        {%- endembed %}
+      {%- endfor %}
+    {%- endif %}
+
+    <h2>{% trans %}Trusted entities{% endtrans %}</h2>
+    {%- if entries.remote is iterable %}
+      {%- for key, set in entries.remote %}
+
+    <fieldset class="fancyfieldset">
+      <legend>{{ mdtype[key]|trans }}</legend>
+      <ul>
+      {% for entityid, entity in set %}
+
+        <li><a href="{{ (metadata_url ~ '?entityid=' ~ entity.entityid ~ '&set=' ~ key) }}">
+          {%- if entity.name_translated %}
+
+          {{ entity.name_translated }}
+          {%- elseif entity.organizationdisplayname_translated %}
+
+          {{ entity.organizationdisplayname_translated }}
+          {%- else %}
+
+          {{ entity.entityid|escape('html') }}
+          {% endif -%}
+
+          </a>
+          {%- if entity.expire %}
+            {%- if entity.expire < date().timestamp %}
+
+          <span class="entity-expired"> (expired {{ ((date().timestamp - entity.expire) / 3600) }} hours ago)</span>
+            {%- else %}
+              {%- set expiration = (entity.expire - date().timestamp) / 3600 %}
+
+          ({% trans %}expires in {{ expiration }} hours{% endtrans %})
+            {%- endif %}
+          {%- endif %}
+
+        </li>
+      {% endfor %}
+      </ul>
+    </fieldset>
+      {% endfor %}
+    {% endif %}
+
+    <h2>{% trans %}Tools{% endtrans %}</h2>
+    <ul>
+      {%- for key, link in links %}
+
+      <li><a href="{{ link.href }}">{{ link.text|trans }}</a></li>
+      {%- endfor %}
+
+    </ul>
+    <form action="{{ metadata_url }}" method="get" class="pure-form">
+      <fieldset class="fancyfieldset">
+        <legend>{% trans %}Look up metadata for entity:{% endtrans %}</legend>
+        <select name="set">
+          {%- if entries.remote %}
+            {%- for key, set in entries.remote %}
+
+          <option value="{{ key|escape }}">{{ mdtype[key]|trans }}</option>
+            {%- endfor %}
+          {%- endif %}
+
+        </select>
+        <input type="text" name="entityid" placeholder="{% trans %}EntityID{% endtrans %}" />
+        <button class="pure-button pure-button-red" type="submit">{% trans %}Search{% endtrans %}</button>
+      </fieldset>
+    </form>
+{% endblock %}
diff --git a/package-lock.json b/package-lock.json
index 299a4db408df509f5d9e459f9d2f1dcf764e385f..39ad954e87fdcea7b94c381acb310f0b2d5bcadc 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1822,6 +1822,11 @@
       "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz",
       "integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM="
     },
+    "highlight.js": {
+      "version": "9.13.1",
+      "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.13.1.tgz",
+      "integrity": "sha512-Sc28JNQNDzaH6PORtRLMvif9RSn1mYuOoX3omVjnb0+HbpPygU2ALBI0R/wsiqCb4/fcp07Gdo8g+fhtFrQl6A=="
+    },
     "jquery": {
       "version": "3.3.1",
       "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.3.1.tgz",
diff --git a/package.json b/package.json
index 088e6515ff9d244bb5a0ac47198bfa49140feaff..6e2f9764d53206213d795081c0c1fa7d488c397b 100644
--- a/package.json
+++ b/package.json
@@ -16,7 +16,8 @@
     "jquery-ui": "^1.12.1",
     "purecss": "^1.0.0",
     "reset-css": "^4.0.1",
-    "selectize": "^0.12.6"
+    "selectize": "^0.12.6",
+    "highlight.js": "^9.13.1"
   },
   "devDependencies": {
     "babel-core": "^6.26.3",
diff --git a/src/css/default.scss b/src/css/default.scss
index 94e0611be0c9f8af30865b772c4f77c7bf813c27..7f63e9ba922c942d518fbb19b747d1f5fb6e23ab 100644
--- a/src/css/default.scss
+++ b/src/css/default.scss
@@ -2,6 +2,7 @@
 @import "../../node_modules/purecss/build/pure.css";
 @import "../../node_modules/font-awesome/scss/font-awesome.scss";
 @import "../../node_modules/selectize/dist/css/selectize.css";
+@import "../../node_modules/highlight.js/styles/zenburn.css";
 
 /************************************************************
  * GENERAL
@@ -47,15 +48,17 @@ body {
 }
 
 h1 {
-  margin: 0.2em 0;
+  margin: 1em 0;
   font-size: 2em;
   font-weight: 900;
 }
 
 h2 {
-  margin: 50px 0 20px 0;
+  margin: 1em 0;
+  font-size: 1.5em;
   font-weight: 700;
   color: #1c1c1c;
+  border-bottom: solid 1px #bbb;
 }
 
 h3 {
@@ -72,10 +75,20 @@ p {
 
 a {
   color: midnightblue;
-}
-
-a:hover {
-  color: rgba(25, 25, 112, 0.5);
+  &:hover,
+  &:focus,
+  &.pure-menu-link:hover,
+  &.pure-menu-link:focus,
+  .pure-menu-selected &.pure-menu-link:hover,
+  .pure-menu-selected &.pure-menu-link:focus {
+    color: white;
+    background-color: #444444;
+    padding: .5em 1em;
+  }
+  &:hover,
+  &:focus {
+    padding: .15rem;
+  }
 }
 
 .dark-bg a {
@@ -122,6 +135,9 @@ pre, code, kbd, samp, tt {
 a.pure-button-red {
   background-color: rgb(219, 1, 0);
   color: #fff;
+  &:hover, &:focus {
+    background-color: #555;
+  }
 }
 
 .pure-button.hollow {
@@ -145,6 +161,16 @@ a.pure-button-red {
   user-select: text;
 }
 
+.pure-button.hljs {
+  display: inline-block;
+  border: 0;
+  background-color: transparent;
+  &:hover, &:focus {
+    background-color: #f0f0f0;
+    color: black;
+  }
+}
+
 .pure-button-group .pure-button:first-child,
 .pure-button-group .pure-button:last-child {
   -webkit-border-radius: 0;
@@ -171,6 +197,12 @@ a.pure-button-red {
   overflow: hidden;
 }
 
+.top-right-corner {
+  position: absolute;
+  right: 1.75em;
+
+}
+
 /* ***********************************************************
 SLIDING SIDE-MENU FOR SMALL SCREENS
 ************************************************************ */
@@ -305,9 +337,12 @@ small screens.
   background: transparent;
   z-index: 10;
   height: 2rem;
-  padding-top: 2rem;
-  padding-bottom: 2rem;
+  padding: 2rem 0;
   text-decoration: none;
+  &:hover, &:focus {
+    padding: 2rem 0;
+    background: none;
+  }
 }
 
 .menu-link span:hover,
@@ -430,14 +465,26 @@ CONTENT
 }
 
 .code-box {
-  border: 1px solid #ccc;
   margin-bottom: 1em;
+  border: 1px solid #ccc;
+  a {
+    padding: .5em;
+  }
 }
 
 .code-box-content {
   font-size: 1em;
   line-height: 1.15;
   padding: 0.5em 1em;
+  display: inline-block;
+  min-height: 1em;
+  max-height: 20em;
+  height: 100%;
+  white-space: pre-wrap;
+  &::selection {
+    color: black;
+    background: #fee9be;
+  }
 }
 
 .code-box-title {
@@ -713,14 +760,16 @@ MEDIA QUERIES
 }
 
 fieldset.fancyfieldset {
-  margin: 2em 1em 1em 0;
-  border: 1px solid #bbb;
+  padding-left: 1.5em;
+  //margin: 2em 1em 1em 0;
+  //border: 1px solid #bbb;
 }
 
 fieldset.fancyfieldset legend {
-  margin-left: 2em;
-  padding: 3px 2em 3px 2em;
-  border: 1px solid #bbb;
+  //margin-left: 2em;
+  padding: 3px 2em 3px 0;
+  //border: 1px solid #bbb;
+  width: 100%;
 }
 
 dt {
@@ -744,3 +793,61 @@ div.preferredidp {
   background: #eee;
   padding: 2px 2em 2px 2em;
 }
+
+
+/*********
+ * Utils *
+ *********/
+
+.clear {
+  clear: both;
+}
+
+.breathe-top {
+  margin-top: 1em;
+}
+
+.expandable {
+  border: solid 1px #bbb;
+  width: 100%;
+
+  .general {
+    padding: 1em;
+  }
+  .content {
+    display: none;
+    padding: 1em;
+  }
+  .expander {
+    cursor: pointer;
+    text-align: center;
+    padding: .25em;
+    display: block;
+    color: black;
+    &:focus, &:hover {
+      background-color: #555;
+      color: white;
+    }
+    &:after {
+      content: "\f078";
+      font-family: FontAwesome;
+    }
+  }
+
+  &.expanded {
+    .content {
+      display: block;
+      border-left: solid .25em #555;
+      border-right: solid .25em #555;
+    }
+    .expander {
+      border-bottom: none;
+      border-top: solid 1px #bbb;
+      border-left: solid .25em #555;
+      border-right: solid .25em #555;
+      &:after {
+        content: "\f077";
+      }
+    }
+  }
+}
diff --git a/src/js/bundle.js b/src/js/bundle.js
index 50aeece058ec5f8aa413e9e2729bfae76f96e235..2766b06fabec8ee653d7e356f357a7e2a3355c01 100644
--- a/src/js/bundle.js
+++ b/src/js/bundle.js
@@ -1,6 +1,9 @@
 import "es6-shim";
-import "clipboard/dist/clipboard";
+import ClipboardJS from "clipboard/dist/clipboard";
 import "selectize/dist/js/selectize";
+import hljs from  "highlight.js/lib/highlight";
+import xml from "highlight.js/lib/languages/xml";
+import php from "highlight.js/lib/languages/php";
 
 $(document).ready(function () {
     // get available languages
@@ -24,4 +27,27 @@ $(document).ready(function () {
         $('#foot').toggleClass('active');
         $(this).toggleClass('active');
     });
+
+    // expander boxes
+    $('.expandable > .expander').on('click', function(e) {
+        e.preventDefault();
+        let target = $(e.currentTarget);
+        target.parents('.expandable').toggleClass('expanded');
+        target.blur();
+    });
+
+    // syntax highlight
+    hljs.registerLanguage('xml', xml);
+    hljs.registerLanguage('php', php);
+    $('.code-box-content.xml, .code-box-content.php').each(function(i, block) {
+        hljs.highlightBlock(block)
+    });
+
+    // clipboard
+    let clipboard = new ClipboardJS('.copy');
+    clipboard.on('success', function(e) {
+        setTimeout(function() {
+            e.clearSelection();
+        }, 150);
+    });
 });
\ No newline at end of file
diff --git a/templates/includes/expander.twig b/templates/includes/expander.twig
new file mode 100644
index 0000000000000000000000000000000000000000..8096b91332caa22e1df26bf548f9f19e31466d7e
--- /dev/null
+++ b/templates/includes/expander.twig
@@ -0,0 +1,11 @@
+        <div class="expandable{% if expanded %} expanded{% endif %}">
+          <div class="general">
+            {%- block general%}{% endblock %}
+
+          </div>
+          <a tabindex="0" class="expander"></a>
+          <div class="content">
+            {%- block content %}{% endblock %}
+
+          </div>
+        </div>
\ No newline at end of file