From 5c529a83aa6d6147d94e5aee996e6b0ab02f0ce4 Mon Sep 17 00:00:00 2001
From: Yassine Doghri <yassine@doghri.fr>
Date: Mon, 8 Nov 2021 16:52:20 +0000
Subject: [PATCH] feat(settings): add theme settings to set an accent color for
 all public pages

set 6 base accent colors: pine, lake, jacaranda, crimson, amber and onyx
---
 app/Config/App.php                            |   2 +
 app/Resources/styles/colorRadioBtn.css        |  26 ++++
 app/Resources/styles/colors.css               |  51 -------
 app/Resources/styles/index.css                |   2 +
 app/Resources/styles/radioBtn.css             |  26 ++--
 app/Resources/styles/themes.css               | 139 ++++++++++++++++++
 .../Components/Forms/ColorRadioButton.php     |  35 +++++
 modules/Admin/Config/Routes.php               |   8 +
 .../Admin/Controllers/SettingsController.php  |  23 ++-
 modules/Admin/Language/en/AdminNavigation.php |  39 -----
 modules/Admin/Language/en/Breadcrumb.php      |   1 +
 modules/Admin/Language/en/Navigation.php      |   4 +
 modules/Admin/Language/en/Settings.php        |  17 ++-
 modules/Admin/Language/fr/AdminNavigation.php |  38 -----
 modules/Admin/Language/fr/Breadcrumb.php      |   2 +
 modules/Admin/Language/fr/Navigation.php      |   3 +
 modules/Admin/Language/fr/Settings.php        |  19 ++-
 tailwind.config.js                            |   1 +
 themes/cp_admin/_partials/_nav_header.php     |   8 +-
 themes/cp_admin/_sidebar.php                  |   6 +-
 themes/cp_admin/settings/general.php          |  16 +-
 themes/cp_admin/settings/theme.php            |  34 +++++
 themes/cp_app/_admin_navbar.php               |   8 +-
 themes/cp_app/_layout.php                     |   5 +-
 themes/cp_app/episode/_layout.php             |   3 +-
 themes/cp_app/home.php                        |  13 +-
 themes/cp_app/map.php                         |   5 +-
 themes/cp_app/page.php                        |   5 +-
 themes/cp_app/podcast/_layout.php             |   3 +-
 .../podcast/_partials/funding_links_modal.php |   4 +-
 themes/cp_app/podcast/about.php               |   4 +-
 themes/cp_app/podcast/follow.php              |  22 ++-
 themes/cp_app/post/remote_action.php          |  16 +-
 33 files changed, 389 insertions(+), 199 deletions(-)
 create mode 100644 app/Resources/styles/colorRadioBtn.css
 create mode 100644 app/Resources/styles/themes.css
 create mode 100644 app/Views/Components/Forms/ColorRadioButton.php
 delete mode 100644 modules/Admin/Language/en/AdminNavigation.php
 delete mode 100644 modules/Admin/Language/fr/AdminNavigation.php
 create mode 100644 themes/cp_admin/settings/theme.php

diff --git a/app/Config/App.php b/app/Config/App.php
index 4d2227c087..2c5e100ff8 100644
--- a/app/Config/App.php
+++ b/app/Config/App.php
@@ -447,4 +447,6 @@ class App extends BaseConfig
         '192' => '/icon-192.png',
         '512' => '/icon-512.png',
     ];
+
+    public string $theme = 'crimson';
 }
diff --git a/app/Resources/styles/colorRadioBtn.css b/app/Resources/styles/colorRadioBtn.css
new file mode 100644
index 0000000000..73a39d5ad3
--- /dev/null
+++ b/app/Resources/styles/colorRadioBtn.css
@@ -0,0 +1,26 @@
+@layer components {
+  .color-radio-btn {
+    @apply absolute opacity-0;
+
+    &:focus + label {
+      @apply ring-accent;
+    }
+
+    &:checked {
+      @apply ring-2 ring-contrast;
+
+      & + label {
+        @apply flex items-center justify-center text-2xl text-accent-contrast bg-accent-base;
+
+        &::before {
+          content: "✓";
+        }
+      }
+    }
+
+    & + label {
+      @apply inline-block w-16 h-16 text-sm font-semibold rounded-full cursor-pointer border-contrast bg-accent-base text-accent-contrast border-3;
+      color: hsl(var(--color-text-muted));
+    }
+  }
+}
diff --git a/app/Resources/styles/colors.css b/app/Resources/styles/colors.css
index eb285c5032..27b5a5b440 100644
--- a/app/Resources/styles/colors.css
+++ b/app/Resources/styles/colors.css
@@ -1,54 +1,3 @@
-/*
---color-brand-lighter: hsl(173 44% 96%);
---color-brand-light: hsl(111 64% 94%);
---color-brand-base: hsl(174 100% 29%);
---color-brand-dark: hsl(172 100% 17%);
---color-brand-darker: hsl(131 100% 12%);
-
---color-background-elevated: hsl(0 0% 100%);
---color-background-base: hsl(173 44% 96%);
-
---color-text-base: hsl(240 17% 2%);
---color-text-muted: hsl(240 8% 63%);
---color-text-inverted: hsl(0 0% 100%);
-
---color-brand-lighter: 173 44% 96%;
---color-brand-light: 111 64% 94%;
---color-brand-base: 174 100% 29%;
---color-brand-dark: 172 100% 17%;
---color-brand-darker: 131 100% 12%;
-
---color-background-elevated: 0 0% 100%;
---color-background-base: 173 44% 96%;
-
---color-text-base: 240 17% 2%;
---color-text-muted: 240 8% 63%;
---color-text-inverted: 0 0% 100%;
-*/
-
-/* 
-    --color-accent-base: 0 100% 38%;
-    --color-accent-hover: 0 100% 48%;
-    --color-accent-muted: 0 8% 63%;
-
-    --color-heading-foreground: 0 64% 94%;
-    --color-heading-background: 0 100% 17%;
-
-    --color-background-elevated: 209 35% 15%;
-    --color-background-base: 210 34% 13%;
-    --color-background-navigation: 210 34% 11%;
-    --color-background-header: 200 38% 15%;
-    --color-background-highlight: 200 38% 25%;
-    --color-background-backdrop: 0 0% 50%;
-
-    --color-border-subtle: 240 8% 27%;
-    --color-border-contrast: 240 8% 78%;
-    --color-border-navigation: 210 34% 4%;
-
-    --color-text-base: 240 17% 100%;
-    --color-text-muted: 240 8% 63%;
-*/
-
 @layer base {
   :root {
     --color-accent-base: 174 100% 29%;
diff --git a/app/Resources/styles/index.css b/app/Resources/styles/index.css
index fb24c42730..62c82209f4 100644
--- a/app/Resources/styles/index.css
+++ b/app/Resources/styles/index.css
@@ -2,10 +2,12 @@
 @import "./custom.css";
 @import "./fonts.css";
 @import "./colors.css";
+@import "./themes.css";
 @import "./breadcrumb.css";
 @import "./dropdown.css";
 @import "./choices.css";
 @import "./radioBtn.css";
+@import "./colorRadioBtn.css";
 @import "./switch.css";
 @import "./radioToggler.css";
 @import "./formInputTabs.css";
diff --git a/app/Resources/styles/radioBtn.css b/app/Resources/styles/radioBtn.css
index 30bbc18ca0..509ec4c650 100644
--- a/app/Resources/styles/radioBtn.css
+++ b/app/Resources/styles/radioBtn.css
@@ -1,22 +1,22 @@
 @layer components {
   .form-radio-btn {
     @apply absolute mt-3 ml-3 border-contrast border-3 text-accent-base;
-  }
 
-  .form-radio-btn:focus + label {
-    @apply ring-accent;
-  }
+    &:focus {
+      @apply ring-accent;
+    }
 
-  .form-radio-btn + label {
-    @apply inline-block py-2 pl-8 pr-2 text-sm font-semibold rounded-lg cursor-pointer border-contrast bg-elevated border-3;
-    color: hsl(var(--color-text-muted));
-  }
+    &:checked {
+      @apply ring-2 ring-contrast;
 
-  .form-radio-btn:checked + label {
-    @apply text-accent-contrast bg-accent-base;
-  }
+      & + label {
+        @apply text-accent-contrast bg-accent-base;
+      }
+    }
 
-  .form-radio-btn:checked {
-    @apply ring-2 ring-contrast;
+    & + label {
+      @apply inline-block py-2 pl-8 pr-2 text-sm font-semibold rounded-lg cursor-pointer border-contrast bg-elevated border-3;
+      color: hsl(var(--color-text-muted));
+    }
   }
 }
diff --git a/app/Resources/styles/themes.css b/app/Resources/styles/themes.css
new file mode 100644
index 0000000000..2df7654a96
--- /dev/null
+++ b/app/Resources/styles/themes.css
@@ -0,0 +1,139 @@
+/* Castopod's brand color */
+.theme-pine {
+  --color-accent-base: 174 100% 29%;
+  --color-accent-hover: 172 100% 17%;
+  --color-accent-muted: 131 100% 12%;
+  --color-accent-contrast: 0 0% 100%;
+
+  --color-heading-foreground: 172 100% 17%;
+  --color-heading-background: 111 64% 94%;
+
+  --color-background-elevated: 0 0% 100%;
+  --color-background-base: 173 44% 96%;
+  --color-background-navigation: 172 100% 17%;
+  --color-background-header: 172 100% 17%;
+  --color-background-highlight: 111 64% 94%;
+  --color-background-backdrop: 0 0% 50%;
+
+  --color-border-subtle: 111 42% 86%;
+  --color-border-contrast: 0 0% 0%;
+  --color-border-navigation: 131 100% 12%;
+
+  --color-text-base: 158 8% 3%;
+  --color-text-muted: 172 8% 38%;
+}
+
+/* Red / Rose color */
+.theme-crimson {
+  --color-accent-base: 350 87% 61%;
+  --color-accent-hover: 348 75% 40%;
+  --color-accent-muted: 348 73% 32%;
+  --color-accent-contrast: 0 0% 100%;
+
+  --color-heading-foreground: 348 73% 32%;
+  --color-heading-background: 344 79% 96%;
+
+  --color-background-elevated: 0 0% 100%;
+  --color-background-base: 350 44% 96%;
+  --color-background-header: 348 75% 40%;
+  --color-background-highlight: 344 79% 96%;
+  --color-background-backdrop: 0 0% 50%;
+
+  --color-border-subtle: 348 42% 86%;
+  --color-border-contrast: 0 0% 0%;
+
+  --color-text-base: 340 8% 3%;
+  --color-text-muted: 345 8% 38%;
+}
+
+/* Blue color */
+.theme-lake {
+  --color-accent-base: 194 100% 44%;
+  --color-accent-hover: 194 100% 22%;
+  --color-accent-muted: 195 100% 11%;
+  --color-accent-contrast: 0 0% 100%;
+
+  --color-heading-foreground: 194 100% 22%;
+  --color-heading-background: 195 100% 92%;
+
+  --color-background-elevated: 0 0% 100%;
+  --color-background-base: 196 44% 96%;
+  --color-background-header: 194 100% 22%;
+  --color-background-highlight: 195 100% 92%;
+  --color-background-backdrop: 0 0% 50%;
+
+  --color-border-subtle: 195 42% 86%;
+  --color-border-contrast: 0 0% 0%;
+
+  --color-text-base: 194 8% 3%;
+  --color-text-muted: 195 8% 38%;
+}
+
+/* Orange color */
+.theme-amber {
+  --color-accent-base: 17 100% 57%;
+  --color-accent-hover: 17 100% 35%;
+  --color-accent-muted: 17 100% 24%;
+  --color-accent-contrast: 0 0% 100%;
+
+  --color-heading-foreground: 17 100% 35%;
+  --color-heading-background: 17 100% 89%;
+
+  --color-background-elevated: 0 0% 100%;
+  --color-background-base: 15 44% 96%;
+  --color-background-header: 17 100% 35%;
+  --color-background-highlight: 17 100% 89%;
+  --color-background-backdrop: 0 0% 50%;
+
+  --color-border-subtle: 17 42% 86%;
+  --color-border-contrast: 0 0% 0%;
+
+  --color-text-base: 15 8% 3%;
+  --color-text-muted: 17 8% 38%;
+}
+
+/* Violet color */
+.theme-jacaranda {
+  --color-accent-base: 254 72% 52%;
+  --color-accent-hover: 254 73% 30%;
+  --color-accent-muted: 254 71% 19%;
+  --color-accent-contrast: 0 0% 100%;
+
+  --color-heading-foreground: 254 73% 30%;
+  --color-heading-background: 254 73% 84%;
+
+  --color-background-elevated: 0 0% 100%;
+  --color-background-base: 253 44% 96%;
+  --color-background-header: 254 73% 30%;
+  --color-background-highlight: 254 88% 91%;
+  --color-background-backdrop: 0 0% 50%;
+
+  --color-border-subtle: 254 42% 86%;
+  --color-border-contrast: 0 0% 0%;
+
+  --color-text-base: 253 8% 3%;
+  --color-text-muted: 254 8% 38%;
+}
+
+/* Black color */
+.theme-onyx {
+  --color-accent-base: 240 17% 2%;
+  --color-accent-hover: 240 17% 17%;
+  --color-accent-muted: 240 17% 17%;
+  --color-accent-contrast: 0 0% 100%;
+
+  --color-heading-foreground: 240 17% 17%;
+  --color-heading-background: 240 17% 94%;
+
+  --color-background-elevated: 0 0% 100%;
+  --color-background-base: 240 17% 96%;
+  --color-background-header: 240 12% 17%;
+  --color-background-highlight: 240 17% 94%;
+  --color-background-backdrop: 0 0% 50%;
+
+  --color-border-subtle: 240 17% 86%;
+  --color-border-contrast: 0 0% 0%;
+
+  --color-text-base: 240 8% 3%;
+  --color-text-muted: 240 8% 38%;
+}
diff --git a/app/Views/Components/Forms/ColorRadioButton.php b/app/Views/Components/Forms/ColorRadioButton.php
new file mode 100644
index 0000000000..906eb69329
--- /dev/null
+++ b/app/Views/Components/Forms/ColorRadioButton.php
@@ -0,0 +1,35 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Views\Components\Forms;
+
+class ColorRadioButton extends FormComponent
+{
+    protected bool $isChecked = false;
+
+    public function setIsChecked(string $value): void
+    {
+        $this->isChecked = $value === 'true';
+    }
+
+    public function render(): string
+    {
+        $radioInput = form_radio(
+            [
+                'id' => $this->value,
+                'name' => $this->name,
+                'class' => 'color-radio-btn',
+            ],
+            $this->value,
+            old($this->name) ? old($this->name) === $this->value : $this->isChecked,
+        );
+
+        return <<<HTML
+            <div class="{$this->class}">
+                {$radioInput}
+                <label for="{$this->value}" title="{$this->slot}" data-tooltip="bottom"></label>
+            </div>
+        HTML;
+    }
+}
diff --git a/modules/Admin/Config/Routes.php b/modules/Admin/Config/Routes.php
index 210f9b3183..7f2435b0ae 100644
--- a/modules/Admin/Config/Routes.php
+++ b/modules/Admin/Config/Routes.php
@@ -31,6 +31,14 @@ $routes->group(
                 'as' => 'settings-instance-delete-icon',
                 'filter' => 'permission:settings-manage',
             ]);
+            $routes->get('theme', 'SettingsController::theme', [
+                'as' => 'settings-theme',
+                'filter' => 'permission:settings-manage',
+            ]);
+            $routes->post('theme', 'SettingsController::attemptSetInstanceTheme', [
+                'as' => 'settings-theme',
+                'filter' => 'permission:settings-manage',
+            ]);
         });
 
         $routes->group('persons', function ($routes): void {
diff --git a/modules/Admin/Controllers/SettingsController.php b/modules/Admin/Controllers/SettingsController.php
index 0362c8233f..d9e9a5318a 100644
--- a/modules/Admin/Controllers/SettingsController.php
+++ b/modules/Admin/Controllers/SettingsController.php
@@ -88,7 +88,7 @@ class SettingsController extends BaseController
                 ]);
         }
 
-        return redirect()->back();
+        return redirect('settings-general')->with('message', lang('Settings.general.instanceEditSuccess'));
     }
 
     public function deleteIcon(): RedirectResponse
@@ -100,6 +100,25 @@ class SettingsController extends BaseController
         service('settings')
             ->forget('App.siteIcon');
 
-        return redirect()->back();
+        return redirect('settings-general')->with('message', lang('Settings.general.deleteIconSuccess'));
+    }
+
+    public function theme(): string
+    {
+        helper('form');
+        return view('settings/theme');
+    }
+
+    public function attemptSetInstanceTheme(): RedirectResponse
+    {
+        $theme = $this->request->getPost('theme');
+        service('settings')
+            ->set('App.theme', $theme);
+
+        // delete all pages cache
+        cache()
+            ->deleteMatching('page*');
+
+        return redirect('settings-theme')->with('message', lang('Settings.theme.setInstanceThemeSuccess'));
     }
 }
diff --git a/modules/Admin/Language/en/AdminNavigation.php b/modules/Admin/Language/en/AdminNavigation.php
deleted file mode 100644
index c2e8e4b696..0000000000
--- a/modules/Admin/Language/en/AdminNavigation.php
+++ /dev/null
@@ -1,39 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-/**
- * @copyright  2020 Podlibre
- * @license    https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
- * @link       https://castopod.org/
- */
-
-return [
-    'go_to_website' => 'View site',
-    'go_to_admin' => 'Go to admin',
-    'dashboard' => 'Dashboard',
-    'admin' => 'Home',
-    'podcasts' => 'Podcasts',
-    'podcast-list' => 'All podcasts',
-    'podcast-create' => 'New podcast',
-    'podcast-import' => 'Import a podcast',
-    'persons' => 'Persons',
-    'person-list' => 'All persons',
-    'person-create' => 'New person',
-    'fediverse' => 'Fediverse',
-    'fediverse-blocked-actors' => 'Blocked accounts',
-    'fediverse-blocked-domains' => 'Blocked domains',
-    'users' => 'Users',
-    'user-list' => 'All users',
-    'user-create' => 'New user',
-    'pages' => 'Pages',
-    'page-list' => 'All pages',
-    'page-create' => 'New Page',
-    'settings' => 'Settings',
-    'settings-general' => 'General',
-    'account' => [
-        'my-account' => 'My account',
-        'change-password' => 'Change password',
-        'logout' => 'Logout',
-    ],
-];
diff --git a/modules/Admin/Language/en/Breadcrumb.php b/modules/Admin/Language/en/Breadcrumb.php
index 63564f79d9..2e8c52b67f 100644
--- a/modules/Admin/Language/en/Breadcrumb.php
+++ b/modules/Admin/Language/en/Breadcrumb.php
@@ -17,6 +17,7 @@ return [
     'contributors' => 'contributors',
     'pages' => 'pages',
     'settings' => 'settings',
+    'theme' => 'theme',
     'add' => 'add',
     'new' => 'new',
     'edit' => 'edit',
diff --git a/modules/Admin/Language/en/Navigation.php b/modules/Admin/Language/en/Navigation.php
index 92a5f59b67..a51ceff891 100644
--- a/modules/Admin/Language/en/Navigation.php
+++ b/modules/Admin/Language/en/Navigation.php
@@ -10,6 +10,7 @@ declare(strict_types=1);
 
 return [
     'go_to_website' => 'View site',
+    'go_to_admin' => 'Go to admin',
     'dashboard' => 'Dashboard',
     'admin' => 'Home',
     'podcasts' => 'Podcasts',
@@ -28,6 +29,9 @@ return [
     'pages' => 'Pages',
     'page-list' => 'All pages',
     'page-create' => 'New Page',
+    'settings' => 'Settings',
+    'settings-general' => 'General',
+    'settings-theme' => 'Theme',
     'account' => [
         'my-account' => 'My account',
         'change-password' => 'Change password',
diff --git a/modules/Admin/Language/en/Settings.php b/modules/Admin/Language/en/Settings.php
index 8864c87fdb..ed97dd4e06 100644
--- a/modules/Admin/Language/en/Settings.php
+++ b/modules/Admin/Language/en/Settings.php
@@ -10,7 +10,7 @@ declare(strict_types=1);
 
 return [
     'title' => 'General settings',
-    'form' => [
+    'general' => [
         'site_section_title' => 'Instance',
         'site_icon' => 'Site icon',
         'site_icon_delete' => 'Delete site icon',
@@ -19,5 +19,20 @@ return [
         'site_name' => 'Site name',
         'site_description' => 'Site description',
         'submit' => 'Save',
+        'instanceEditSuccess' => 'Instance has been updated successfully!',
+        'deleteIconSuccess' => 'Site icon has been remove successfully!',
+    ],
+    'theme' => [
+        'title' => 'Theme',
+        'accent_section_title' => 'Accent color',
+        'accent_section_subtitle' => 'Choose the color to determine the look and feel of all public pages.',
+        'pine' => 'Pine',
+        'crimson' => 'Crimson',
+        'amber' => 'Amber',
+        'lake' => 'Lake',
+        'jacaranda' => 'Jacaranda',
+        'onyx' => 'Onyx',
+        'submit' => 'Save',
+        'setInstanceThemeSuccess' => 'Theme has been updated successfully!',
     ],
 ];
diff --git a/modules/Admin/Language/fr/AdminNavigation.php b/modules/Admin/Language/fr/AdminNavigation.php
deleted file mode 100644
index b85b1a3025..0000000000
--- a/modules/Admin/Language/fr/AdminNavigation.php
+++ /dev/null
@@ -1,38 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-/**
- * @copyright  2020 Podlibre
- * @license    https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
- * @link       https://castopod.org/
- */
-
-return [
-    'go_to_website' => 'Visiter le site',
-    'dashboard' => 'Tableau de bord',
-    'admin' => 'Accueil',
-    'podcasts' => 'Podcasts',
-    'podcast-list' => 'Tous les podcasts',
-    'podcast-create' => 'Créer un podcast',
-    'podcast-import' => 'Importer un podcast',
-    'persons' => 'Intervenants',
-    'person-list' => 'Tous les intervenants',
-    'person-create' => 'Nouvel intervenant',
-    'fediverse' => 'Fédiverse',
-    'fediverse-blocked_actors' => 'Utilisateurs blockés',
-    'fediverse-blocked_domains' => 'Domaines blockés',
-    'users' => 'Utilisateurs',
-    'user-list' => 'Tous les utilisateurs',
-    'user-create' => 'Créer un utilisateur',
-    'pages' => 'Pages',
-    'page-list' => 'Toutes les pages',
-    'page-create' => 'Créer une page',
-    'settings' => 'Paramètres',
-    'settings-general' => 'Général',
-    'account' => [
-        'my-account' => 'Mon compte',
-        'change-password' => 'Modifier le mot de passe',
-        'logout' => 'Déconnexion',
-    ],
-];
diff --git a/modules/Admin/Language/fr/Breadcrumb.php b/modules/Admin/Language/fr/Breadcrumb.php
index 91b6e0d92a..d1bfa0fc95 100644
--- a/modules/Admin/Language/fr/Breadcrumb.php
+++ b/modules/Admin/Language/fr/Breadcrumb.php
@@ -16,6 +16,8 @@ return [
     'episodes' => 'épisodes',
     'contributors' => 'contributeurs',
     'pages' => 'pages',
+    'settings' => 'paramètres',
+    'theme' => 'thème',
     'add' => 'ajouter',
     'new' => 'créer',
     'edit' => 'modifier',
diff --git a/modules/Admin/Language/fr/Navigation.php b/modules/Admin/Language/fr/Navigation.php
index 357b64ffab..469a43edeb 100644
--- a/modules/Admin/Language/fr/Navigation.php
+++ b/modules/Admin/Language/fr/Navigation.php
@@ -28,6 +28,9 @@ return [
     'pages' => 'Pages',
     'page-list' => 'Toutes les pages',
     'page-create' => 'Créer une page',
+    'settings' => 'Paramètres',
+    'settings-general' => 'Général',
+    'settings-themes' => 'Thèmes',
     'account' => [
         'my-account' => 'Mon compte',
         'change-password' => 'Modifier le mot de passe',
diff --git a/modules/Admin/Language/fr/Settings.php b/modules/Admin/Language/fr/Settings.php
index 08cb0a64ac..35e603af40 100644
--- a/modules/Admin/Language/fr/Settings.php
+++ b/modules/Admin/Language/fr/Settings.php
@@ -10,15 +10,28 @@ declare(strict_types=1);
 
 return [
     'title' => 'Paramètres généraux',
-    'form' => [
+    'general' => [
         'site_section_title' => 'Instance',
-        'site_section_subtitle' => 'configuration de l’instance',
         'site_icon' => 'Favicon du site',
         'site_icon_delete' => 'Supprimer la favicon du site',
         'site_icon_hint' => 'Les favicons sont ce que vous voyez sur les onglets de votre navigateur, dans votre barre de favoris, et lorsque vous ajoutez un site web en raccourci sur des appareils mobiles.',
         'site_icon_helper' => 'La favicon doit être carrée, avec au minimum 512px de largeur et de hauteur.',
         'site_name' => 'Titre du site',
         'site_description' => 'Description du site',
-        'submit' => 'Save',
+        'submit' => 'Sauvegarder',
+        'instanceEditSuccess' => 'L’instance a bien été mise à jour !',
+        'deleteIconSuccess' => 'La favicon du site a bien été retirée !',
+    ],
+    'theme' => [
+        'title' => 'Thème',
+        'accent_section_title' => 'Couleur d’accentuation',
+        'accent_section_subtitle' => 'Sélectionnez une couleur qui déterminera l’apparence de toutes les pages publiques.',
+        'pine' => 'Pin',
+        'crimson' => 'Cramoisi',
+        'amber' => 'Ambre',
+        'lake' => 'Lac',
+        'jacaranda' => 'Jacaranda',
+        'onyx' => 'Onyx',
+        'setInstanceThemeSuccess' => 'Le thème a bien été mis à jour !',
     ],
 ];
diff --git a/tailwind.config.js b/tailwind.config.js
index 5e6b5c9393..e902d879f3 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -107,6 +107,7 @@ module.exports = {
         podcastMain: "1fr minmax(200px, 300px)",
         cards: "repeat(auto-fill, minmax(14rem, 1fr))",
         latestEpisodes: "repeat(5, 1fr)",
+        colorButtons: "repeat(auto-fill, minmax(4rem, 1fr))",
       },
       gridTemplateRows: {
         admin: "40px 1fr",
diff --git a/themes/cp_admin/_partials/_nav_header.php b/themes/cp_admin/_partials/_nav_header.php
index 5ec745c31e..6152c47e85 100644
--- a/themes/cp_admin/_partials/_nav_header.php
+++ b/themes/cp_admin/_partials/_nav_header.php
@@ -11,7 +11,7 @@
         <a href="<?= route_to(
     'home',
 ) ?>" class="inline-flex items-center h-full px-6 text-sm font-semibold hover:underline focus:ring-inset focus:ring-accent">
-                <?= lang('AdminNavigation.go_to_website') ?>
+                <?= lang('Navigation.go_to_website') ?>
                 <?= icon('external-link', 'ml-1 opacity-60') ?>
         </a>
     </div>
@@ -48,12 +48,12 @@
         $menuItems = [
             [
                 'type' => 'link',
-                'title' => lang('AdminNavigation.account.my-account'),
+                'title' => lang('Navigation.account.my-account'),
                 'uri' => route_to('my-account'),
             ],
             [
                 'type' => 'link',
-                'title' => lang('AdminNavigation.account.change-password'),
+                'title' => lang('Navigation.account.change-password'),
                 'uri' => route_to('change-password'),
             ],
             [
@@ -61,7 +61,7 @@
             ],
             [
                 'type' => 'link',
-                'title' => lang('AdminNavigation.account.logout'),
+                'title' => lang('Navigation.account.logout'),
                 'uri' => route_to('logout'),
             ],
         ];
diff --git a/themes/cp_admin/_sidebar.php b/themes/cp_admin/_sidebar.php
index 353f279994..8f53e8153d 100644
--- a/themes/cp_admin/_sidebar.php
+++ b/themes/cp_admin/_sidebar.php
@@ -24,7 +24,7 @@ $navigation = [
     ],
     'settings' => [
         'icon' => 'settings',
-        'items' => ['settings-general'],
+        'items' => ['settings-general', 'settings-theme'],
     ],
 ]; ?>
 
@@ -33,7 +33,7 @@ $navigation = [
     <div>
         <button class="inline-flex items-center w-full px-4 py-1 font-semibold focus:ring-accent" type="button">
             <?= icon($data['icon'], 'opacity-60 text-2xl mr-4') ?>
-            <?= lang('AdminNavigation.' . $section) ?>
+            <?= lang('Navigation.' . $section) ?>
         </button>
         <ul class="flex flex-col">
             <?php foreach ($data['items'] as $item): ?>
@@ -42,7 +42,7 @@ $navigation = [
                 <a class="w-full py-1 pl-14 pr-2 text-sm hover:opacity-100 focus:ring-inset focus:ring-accent<?= $isActive
                     ? ' font-semibold opacity-100 inline-flex items-center'
                     : ' opacity-75' ?>" href="<?= route_to($item) ?>"><?= ($isActive ? icon('chevron-right', 'mr-2') : '') . lang(
-                        'AdminNavigation.' . $item,
+                        'Navigation.' . $item,
                     ) ?></a>
             </li>
             <?php endforeach; ?>
diff --git a/themes/cp_admin/settings/general.php b/themes/cp_admin/settings/general.php
index 987adf54ca..33d1feb7d3 100644
--- a/themes/cp_admin/settings/general.php
+++ b/themes/cp_admin/settings/general.php
@@ -14,11 +14,11 @@
 <?= csrf_field() ?>
 
 <Forms.Section
-    title="<?= lang('Settings.form.site_section_title') ?>">
+    title="<?= lang('Settings.general.site_section_title') ?>">
 
     <Forms.Field
         name="site_name"
-        label="<?= lang('Settings.form.site_name') ?>"
+        label="<?= lang('Settings.general.site_name') ?>"
         value="<?= service('settings')
     ->get('App.siteName') ?>"
         required="true" />
@@ -26,7 +26,7 @@
     <Forms.Field
         as="Textarea"
         name="site_description"
-        label="<?= lang('Settings.form.site_description') ?>"
+        label="<?= lang('Settings.general.site_description') ?>"
         value="<?= service('settings')
     ->get('App.siteDescription') ?>"
         required="true"
@@ -36,21 +36,21 @@
         <Forms.Field
             name="site_icon"
             type="file"
-            label="<?= lang('Settings.form.site_icon') ?>"
-            hint="<?= lang('Settings.form.site_icon_hint') ?>"
-            helper="<?= lang('Settings.form.site_icon_helper') ?>"
+            label="<?= lang('Settings.general.site_icon') ?>"
+            hint="<?= lang('Settings.general.site_icon_hint') ?>"
+            helper="<?= lang('Settings.general.site_icon_helper') ?>"
             accept=".png,.jpeg,.jpg"
             class="flex-1"
             />
         <?php if (config('App')->siteIcon['ico'] !== service('settings')->get('App.siteIcon')['ico']): ?>
         <div class="relative ml-2">
-            <a href="<?= route_to('settings-instance-delete-icon') ?>" class="absolute p-1 text-red-700 bg-red-100 border-2 rounded-full hover:text-red-900 border-contrast -top-3 -right-3 focus:ring-accent" title="<?= lang('Settings.form.site_icon_delete') ?>" data-tooltip="top"><?= icon('delete-bin') ?></a>
+            <a href="<?= route_to('settings-instance-delete-icon') ?>" class="absolute p-1 text-red-700 bg-red-100 border-2 rounded-full hover:text-red-900 border-contrast -top-3 -right-3 focus:ring-accent" title="<?= lang('Settings.general.site_icon_delete') ?>" data-tooltip="top"><?= icon('delete-bin') ?></a>
             <img src="<?= service('settings')->get('App.siteIcon')['64'] ?>" alt="<?= service('settings')->get('App.siteName') ?> Favicon" class="w-10 h-10" />
         </div>
         <?php endif; ?>
     </div>
 
-    <Button variant="primary" type="submit" class="self-end"><?= lang('Settings.form.submit') ?></Button>
+    <Button variant="primary" type="submit" class="self-end"><?= lang('Settings.general.submit') ?></Button>
 
 </Forms.Section>
 
diff --git a/themes/cp_admin/settings/theme.php b/themes/cp_admin/settings/theme.php
new file mode 100644
index 0000000000..6cf76d50c5
--- /dev/null
+++ b/themes/cp_admin/settings/theme.php
@@ -0,0 +1,34 @@
+<?= $this->extend('_layout') ?>
+
+<?= $this->section('title') ?>
+<?= lang('Settings.theme.title') ?>
+<?= $this->endSection() ?>
+
+<?= $this->section('pageTitle') ?>
+<?= lang('Settings.theme.title') ?>
+<?= $this->endSection() ?>
+
+<?= $this->section('content') ?>
+
+<form action="<?= route_to('settings-theme') ?>" method="POST" class="flex flex-col gap-y-4" enctype="multipart/form-data">
+<?= csrf_field() ?>
+<Forms.Section
+    title="<?= lang('Settings.theme.accent_section_title') ?>"
+    subtitle="<?= lang('Settings.theme.accent_section_subtitle') ?>">
+
+<div class="grid gap-4 grid-cols-colorButtons">
+    <?php foreach (['pine', 'lake', 'jacaranda', 'crimson', 'amber', 'onyx'] as $theme): ?>
+        <Forms.ColorRadioButton
+        class="theme-<?= $theme ?> mx-auto"
+        value="<?= $theme ?>"
+        name="theme"
+        isChecked="<?= $theme === service('settings')->get('App.theme') ? 'true' : 'false' ?>" ><?= lang('Settings.theme.' . $theme) ?></Forms.ColorRadioButton>
+    <?php endforeach; ?>
+</div>
+
+<Button variant="primary" type="submit" class="self-end"><?= lang('Settings.theme.submit') ?></Button>
+
+</Forms.Section>
+
+</form>
+<?= $this->endSection() ?>
\ No newline at end of file
diff --git a/themes/cp_app/_admin_navbar.php b/themes/cp_app/_admin_navbar.php
index 6d66f6626f..5b704bddc6 100644
--- a/themes/cp_app/_admin_navbar.php
+++ b/themes/cp_app/_admin_navbar.php
@@ -4,7 +4,7 @@
                     <?= svg('castopod-logo-base', 'h-6') ?>
             </a>
             <a href="<?= route_to('admin', ) ?>" class="inline-flex items-center h-full px-6 text-sm font-semibold hover:underline focus:ring-inset focus:ring-accent">
-                <?= lang('AdminNavigation.go_to_admin') ?>
+                <?= lang('Navigation.go_to_admin') ?>
                 <?= icon('external-link', 'ml-1 opacity-60') ?>
             </a>
         </div>
@@ -43,12 +43,12 @@
             $menuItems = [
                 [
                     'type' => 'link',
-                    'title' => lang('AdminNavigation.account.my-account'),
+                    'title' => lang('Navigation.account.my-account'),
                     'uri' => route_to('my-account'),
                 ],
                 [
                     'type' => 'link',
-                    'title' => lang('AdminNavigation.account.change-password'),
+                    'title' => lang('Navigation.account.change-password'),
                     'uri' => route_to('change-password'),
                 ],
                 [
@@ -56,7 +56,7 @@
                 ],
                 [
                     'type' => 'link',
-                    'title' => lang('AdminNavigation.account.logout'),
+                    'title' => lang('Navigation.account.logout'),
                     'uri' => route_to('logout'),
                 ],
             ];
diff --git a/themes/cp_app/_layout.php b/themes/cp_app/_layout.php
index 2cdf1c0f0f..d80ecb993a 100644
--- a/themes/cp_app/_layout.php
+++ b/themes/cp_app/_layout.php
@@ -29,12 +29,13 @@
         ->asset('js/audio-player.ts', 'js') ?>
 </head>
 
-<body class="flex flex-col min-h-screen mx-auto bg-base">
+<body class="flex flex-col min-h-screen mx-auto bg-base theme-<?= service('settings')
+        ->get('App.theme') ?>">
     <?php if (service('authentication')->check()): ?>
         <?= $this->include('_admin_navbar') ?>
     <?php endif; ?>
 
-    <header class="py-8 text-white border-b bg-elevated border-subtle">
+    <header class="py-8 border-b bg-elevated border-subtle">
         <div class="container flex flex-col items-start px-2 py-4 mx-auto">
             <a href="<?= route_to('home') ?>"
             class="inline-flex items-center mb-2 text-sm focus:ring-accent"><?= icon(
diff --git a/themes/cp_app/episode/_layout.php b/themes/cp_app/episode/_layout.php
index 0c1bea56d9..0142abdc80 100644
--- a/themes/cp_app/episode/_layout.php
+++ b/themes/cp_app/episode/_layout.php
@@ -27,7 +27,8 @@
         ->asset('js/audio-player.ts', 'js') ?>
 </head>
 
-<body class="flex flex-col min-h-screen mx-auto md:min-h-full md:grid md:grid-cols-podcast bg-base">
+<body class="flex flex-col min-h-screen mx-auto md:min-h-full md:grid md:grid-cols-podcast bg-base theme-<?= service('settings')
+        ->get('App.theme') ?>">
     <?php if (can_user_interact()): ?>
         <div class="col-span-full">
             <?= $this->include('_admin_navbar') ?>
diff --git a/themes/cp_app/home.php b/themes/cp_app/home.php
index 92779e02e6..f15b2b9e60 100644
--- a/themes/cp_app/home.php
+++ b/themes/cp_app/home.php
@@ -28,7 +28,8 @@
         ->asset('js/app.ts', 'js') ?>
 </head>
 
-<body class="flex flex-col min-h-screen mx-auto bg-base">
+<body class="flex flex-col min-h-screen mx-auto bg-base theme-<?= service('settings')
+        ->get('App.theme') ?>">
     <?php if (service('authentication')->check()): ?>
         <?= $this->include('_admin_navbar') ?>
     <?php endif; ?>
@@ -37,14 +38,16 @@
         <div class="container flex items-center justify-between px-2 py-4 mx-auto">
             <a href="<?= route_to(
             'home',
-        ) ?>" class="inline-flex items-baseline text-3xl font-semibold font-display"><?= 'castopod' .
-    svg('castopod-logo-base', 'h-6 ml-2') ?></a>
+        ) ?>" class="inline-flex items-baseline text-3xl font-semibold font-display"><?= service('settings')
+        ->get('App.siteName') === 'Castopod' ? 'castopod' .
+    svg('castopod-logo-base', 'h-6 ml-2') : service('settings')
+        ->get('App.siteName') ?></a>
         </div>
     </header>
     <main class="container flex-1 px-4 py-10 mx-auto">
         <Heading class="inline-block mb-2"><?= lang('Home.all_podcasts') ?> (<?= count(
-        $podcasts,
-    ) ?>)</Heading>
+            $podcasts,
+        ) ?>)</Heading>
         <section class="grid gap-4 grid-cols-cards">
             <?php if ($podcasts): ?>
                 <?php foreach ($podcasts as $podcast): ?>
diff --git a/themes/cp_app/map.php b/themes/cp_app/map.php
index 1a1d41ce04..f064a54fdf 100644
--- a/themes/cp_app/map.php
+++ b/themes/cp_app/map.php
@@ -20,12 +20,13 @@
         ->asset('js/map.ts', 'js') ?>
 </head>
 
-<body class="flex flex-col h-full min-h-screen mx-auto bg-base">
+<body class="flex flex-col h-full min-h-screen mx-auto bg-base theme-<?= service('settings')
+        ->get('App.theme') ?>">
     <?php if (service('authentication')->check()): ?>
         <?= $this->include('_admin_navbar') ?>
     <?php endif; ?>
 
-    <header class="py-8 text-white border-b border-subtle bg-elevated">
+    <header class="py-8 border-b border-subtle bg-elevated">
         <div class="container flex flex-col items-start px-2 py-4 mx-auto">
             <a href="<?= route_to('home') ?>"
             class="inline-flex items-center mb-2 text-sm focus:ring-accent"><?= icon(
diff --git a/themes/cp_app/page.php b/themes/cp_app/page.php
index c4ac04e7e1..148f3152fc 100644
--- a/themes/cp_app/page.php
+++ b/themes/cp_app/page.php
@@ -17,7 +17,8 @@
         ->asset('js/app.ts', 'js') ?>
 </head>
 
-<body class="flex flex-col min-h-screen mx-auto bg-base">
+<body class="flex flex-col min-h-screen mx-auto bg-base theme-<?= service('settings')
+        ->get('App.theme') ?>">
     <?php if (service('authentication')->check()): ?>
         <?= $this->include('_admin_navbar') ?>
     <?php endif; ?>
@@ -29,7 +30,7 @@
             'arrow-left',
             'mr-2',
         ) . lang('Page.back_to_home') ?></a>
-            <h1 class="text-3xl font-semibold"><?= $page->title ?></h1>
+            <Heading tagName="h1" class="text-3xl font-semibold"><?= $page->title ?></Heading>
         </div>
     </header>
     <main class="container flex-1 px-4 py-10 mx-auto">
diff --git a/themes/cp_app/podcast/_layout.php b/themes/cp_app/podcast/_layout.php
index fda22ff22b..cf38f6d0ef 100644
--- a/themes/cp_app/podcast/_layout.php
+++ b/themes/cp_app/podcast/_layout.php
@@ -27,7 +27,8 @@
         ->asset('js/audio-player.ts', 'js') ?>
 </head>
 
-<body class="flex flex-col min-h-screen mx-auto md:min-h-full md:grid md:grid-cols-podcast bg-base">
+<body class="flex flex-col min-h-screen mx-auto md:min-h-full md:grid md:grid-cols-podcast bg-base theme-<?= service('settings')
+        ->get('App.theme') ?>">
     <?php if (can_user_interact()): ?>
         <div class="col-span-full">
             <?= $this->include('_admin_navbar') ?>
diff --git a/themes/cp_app/podcast/_partials/funding_links_modal.php b/themes/cp_app/podcast/_partials/funding_links_modal.php
index e528e17a03..aff193c455 100644
--- a/themes/cp_app/podcast/_partials/funding_links_modal.php
+++ b/themes/cp_app/podcast/_partials/funding_links_modal.php
@@ -14,7 +14,7 @@
             data-toggle="funding-links"
             data-toggle-class="hidden"
             aria-label="<?= lang('Common.close') ?>"
-            class="self-start p-1 text-2xl"><?= icon('close') ?></button>
+            class="self-start p-1 text-2xl rounded-full focus:ring-accent"><?= icon('close') ?></button>
         </div>
         <div class="flex flex-col items-start p-4 space-y-4">
             <?php foreach ($podcast->fundingPlatforms as $fundingPlatform): ?>
@@ -24,7 +24,7 @@
                     title="<?= $fundingPlatform->link_content ?>"
                     target="_blank"
                     rel="noopener noreferrer"
-                    class="inline-flex items-center font-semibold text-accent-base">
+                    class="inline-flex items-center font-semibold text-accent-base hover:text-accent-hover focus:ring-accent">
                     <?= icon(
                 $fundingPlatform->type .
                             '/' .
diff --git a/themes/cp_app/podcast/about.php b/themes/cp_app/podcast/about.php
index 9d914d6172..ce00cbb117 100644
--- a/themes/cp_app/podcast/about.php
+++ b/themes/cp_app/podcast/about.php
@@ -33,13 +33,13 @@
 <div class="px-2 sm:px-4">
     <div class="mb-2"><?= $podcast->description_html ?></div>
     <div class="flex gap-x-4 gap-y-2">
-        <span class="px-2 py-1 text-sm font-semibold border rounded-sm border-subtle bg-highlight text-skin-muted">
+        <span class="px-2 py-1 text-sm font-semibold border rounded-sm border-subtle bg-highlight">
             <?= lang(
         'Podcast.category_options.' . $podcast->category->code,
     ) ?>
         </span>
         <?php foreach ($podcast->other_categories as $other_category): ?>
-            <span class="px-2 py-1 text-sm font-semibold border rounded-sm border-subtle bg-highlight text-skin-muted">
+            <span class="px-2 py-1 text-sm font-semibold border rounded-sm border-subtle bg-highlight">
                 <?= lang(
         'Podcast.category_options.' . $other_category->code,
     ) ?>
diff --git a/themes/cp_app/podcast/follow.php b/themes/cp_app/podcast/follow.php
index 3ff1b93e64..c491d46e09 100644
--- a/themes/cp_app/podcast/follow.php
+++ b/themes/cp_app/podcast/follow.php
@@ -33,9 +33,10 @@
 </head>
 
 
-<body class="flex flex-col min-h-screen bg-base">
+<body class="flex flex-col min-h-screen bg-base theme-<?= service('settings')
+        ->get('App.theme') ?>">
     <header class="flex flex-col items-center mb-8">
-        <h1 class="w-full pt-8 pb-32 text-lg font-semibold text-center text-skin-base bg-elevated"><?= lang(
+        <h1 class="w-full pt-8 pb-32 text-lg font-semibold text-center text-white bg-header"><?= lang(
             'Fediverse.follow.subtitle',
         ) ?></h1>
         <div class="flex flex-col w-full max-w-xs -mt-24 overflow-hidden shadow bg-elevated rounded-xl">
@@ -52,7 +53,7 @@
     </header>
 
     <main class="w-full max-w-md px-4 mx-auto">
-        <form action="<?= route_to('attempt-follow', $actor->username) ?>" method="POST" class="flex flex-col">
+        <form action="<?= route_to('attempt-follow', $actor->username) ?>" method="POST" class="flex flex-col gap-y-2">
             <?= csrf_field() ?>
             <?= view('_message_block') ?>
 
@@ -62,18 +63,15 @@
                 hint="<?= lang('Fediverse.your_handle_hint') ?>"
                 required="true"
             />
-            <Button variant="primary" type="submit" class="self-end mt-2" iconRight="send-plane"><?= lang('Fediverse.follow.submit') ?></Button>
+            <Button variant="primary" type="submit" class="self-end" iconRight="send-plane"><?= lang('Fediverse.follow.submit') ?></Button>
         </form>
     </main>
 
     <footer
-        class="container flex flex-col items-center px-2 py-4 mx-auto mt-auto text-xs border-t md:justify-between md:flex-row">
-        <?= render_page_links('inline-flex mb-4 md:mb-0') ?>
-        <p>
-            <?= lang('Common.powered_by', [
-                'castopod' =>
-                    '<a class="inline-flex font-semibold hover:underline focus:ring-accent" href="https://castopod.org" target="_blank" rel="noreferrer noopener">Castopod' . icon('social/castopod', 'ml-1 text-lg') . '</a>',
-            ]) ?>
-        </p>
+        class="flex-col w-full px-2 py-4 mt-auto text-xs text-center border-t text-skin-muted border-subtle">
+        <?= lang('Common.powered_by', [
+            'castopod' =>
+                '<a class="inline-flex font-semibold hover:underline focus:ring-accent" href="https://castopod.org" target="_blank" rel="noreferrer noopener">Castopod' . icon('social/castopod', 'ml-1 text-lg') . '</a>',
+        ]) ?>
     </footer>
 </body>
diff --git a/themes/cp_app/post/remote_action.php b/themes/cp_app/post/remote_action.php
index 3ad0174497..2dd36fe500 100644
--- a/themes/cp_app/post/remote_action.php
+++ b/themes/cp_app/post/remote_action.php
@@ -34,9 +34,10 @@
         ->asset('js/podcast.ts', 'js') ?>
 </head>
 
-<body class="min-h-screen mx-auto bg-base">
-    <header class="pt-8 pb-32 bg-elevated">
-        <h1 class="text-lg font-semibold text-center text-skin-base"><?= lang(
+<body class="flex flex-col min-h-screen mx-auto bg-base theme-<?= service('settings')
+        ->get('App.theme') ?>">
+    <header class="pt-8 pb-32 bg-header">
+        <h1 class="text-lg font-semibold text-center text-white"><?= lang(
             'Fediverse.' . $action . '.subtitle',
         ) ?></h1>
     </header>
@@ -47,7 +48,7 @@
             'post' => $post,
         ]) ?>
 
-        <form action="<?= route_to('post-attempt-remote-action', $post->id, $action) ?>" method="POST" class="flex flex-col mt-8">
+        <form action="<?= route_to('post-attempt-remote-action', $post->id, $action) ?>" method="POST" class="flex flex-col mt-8 gap-y-2">
             <?= csrf_field() ?>
             <?= view('_message_block') ?>
 
@@ -60,4 +61,11 @@
             <Button variant="primary" type="submit" class="self-end" iconRight="send-plane"><?= lang('Fediverse.' . $action . '.submit') ?></Button>
         </form>
     </main>
+    <footer
+        class="flex-col w-full px-2 py-4 mt-auto text-xs text-center border-t text-skin-muted border-subtle">
+        <?= lang('Common.powered_by', [
+            'castopod' =>
+                '<a class="inline-flex font-semibold hover:underline focus:ring-accent" href="https://castopod.org" target="_blank" rel="noreferrer noopener">Castopod' . icon('social/castopod', 'ml-1 text-lg') . '</a>',
+        ]) ?>
+    </footer>
 </body>
-- 
GitLab