You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by ma...@apache.org on 2026/01/15 11:45:08 UTC

(grails-core) branch app-welcome-page created (now e20c91bef6)

This is an automated email from the ASF dual-hosted git repository.

matrei pushed a change to branch app-welcome-page
in repository https://gitbox.apache.org/repos/asf/grails-core.git


      at e20c91bef6 fix: improve new app welcome page

This branch includes the following new commits:

     new e20c91bef6 fix: improve new app welcome page

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(grails-core) 01/01: fix: improve new app welcome page

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

matrei pushed a commit to branch app-welcome-page
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit e20c91bef6eb1956238cada650f1e18b5865dbaf
Author: Mattias Reichel <ma...@gmail.com>
AuthorDate: Thu Jan 15 12:43:40 2026 +0100

    fix: improve new app welcome page
---
 .../resources/assets/images/advancedgrails.svg     |   6 +-
 .../main/resources/assets/images/documentation.svg |   4 +-
 .../src/main/resources/assets/images/favicon.ico   | Bin 5558 -> 27198 bytes
 .../src/main/resources/assets/images/groovy.svg    |   1 +
 .../src/main/resources/assets/images/java.svg      |   1 +
 .../main/resources/assets/images/spring-boot.svg   |  20 ++
 .../src/main/resources/assets/images/spring.svg    |   1 +
 .../main/resources/assets/javascripts/welcome.js   | 116 ++++++
 .../main/resources/assets/stylesheets/welcome.css  |  63 ++++
 .../src/main/resources/gsp/index.gsp               | 397 +++++++++++++++++----
 .../src/main/resources/gsp/main.gsp                | 113 +++---
 .../grails-app/assets/images/advancedgrails.svg    |   6 +-
 .../grails-app/assets/images/documentation.svg     |   4 +-
 .../skeleton/grails-app/assets/images/favicon.ico  | Bin 5558 -> 27198 bytes
 .../skeleton/grails-app/assets/images/groovy.svg   |   1 +
 .../web/skeleton/grails-app/assets/images/java.svg |   1 +
 .../grails-app/assets/images/spring-boot.svg       |  20 ++
 .../skeleton/grails-app/assets/images/spring.svg   |   1 +
 .../grails-app/assets/javascripts/welcome.js       | 115 ++++++
 .../grails-app/assets/stylesheets/welcome.css      |  63 ++++
 .../web/skeleton/grails-app/views/index.gsp        | 397 +++++++++++++++++----
 .../web/skeleton/grails-app/views/layouts/main.gsp | 113 +++---
 22 files changed, 1187 insertions(+), 256 deletions(-)

diff --git a/grails-forge/grails-forge-core/src/main/resources/assets/images/advancedgrails.svg b/grails-forge/grails-forge-core/src/main/resources/assets/images/advancedgrails.svg
index 8b63ec8be5..9036e3cf5f 100644
--- a/grails-forge/grails-forge-core/src/main/resources/assets/images/advancedgrails.svg
+++ b/grails-forge/grails-forge-core/src/main/resources/assets/images/advancedgrails.svg
@@ -5,14 +5,14 @@
 	 width="93.58px" height="93.58px" viewBox="0 0 93.58 93.58" enable-background="new 0 0 93.58 93.58" xml:space="preserve">
 <g>
 	<g>
-		<circle fill="none" stroke="#FEB672" stroke-width="2.8347" stroke-miterlimit="10" cx="46.79" cy="46.789" r="45.374"/>
+		<circle fill="#FEB672" stroke="#FEB672" stroke-width="2.8347" stroke-miterlimit="10" cx="46.79" cy="46.789" r="45.374"/>
 	</g>
 	<g>
-		<path fill="#FEB672" d="M71.126,29.576c0,0.414-0.337,0.75-0.75,0.75h-3.25v3.25c0,0.415-0.337,0.751-0.751,0.751h-1.499
+		<path fill="white" d="M71.126,29.576c0,0.414-0.337,0.75-0.75,0.75h-3.25v3.25c0,0.415-0.337,0.751-0.751,0.751h-1.499
 			c-0.415,0-0.75-0.336-0.75-0.751v-3.25h-3.251c-0.414,0-0.749-0.336-0.749-0.75v-1.498c0-0.416,0.335-0.752,0.749-0.752h3.251
 			v-3.249c0-0.414,0.335-0.75,0.75-0.75h1.499c0.414,0,0.751,0.336,0.751,0.75v3.249h3.25c0.413,0,0.75,0.336,0.75,0.752V29.576z"/>
 	</g>
-	<path fill="#FEB672" d="M50.42,60.386c0.554,1.467,0.855,1.951,1.493,3.44c0.271,0.627,0.523,1.228,0.649,1.518
+	<path fill="white" d="M50.42,60.386c0.554,1.467,0.855,1.951,1.493,3.44c0.271,0.627,0.523,1.228,0.649,1.518
 		c0.049,0.117,0.036,0.248-0.033,0.355c-0.172,0.259-0.552,0.747-1.181,1.086c-1.098,0.594-3.409,0.809-4.555,0.812h-0.006
 		c-1.146-0.004-3.457-0.219-4.558-0.812c-0.627-0.339-1.006-0.827-1.177-1.086c-0.07-0.107-0.083-0.238-0.032-0.355
 		c0.123-0.29,0.376-0.891,0.646-1.518c0.64-1.489,0.941-1.974,1.495-3.44c0.485-1.294,0.729-3.175,0.745-4.593
diff --git a/grails-forge/grails-forge-core/src/main/resources/assets/images/documentation.svg b/grails-forge/grails-forge-core/src/main/resources/assets/images/documentation.svg
index 29bc9d57d3..78ff4dc4b9 100644
--- a/grails-forge/grails-forge-core/src/main/resources/assets/images/documentation.svg
+++ b/grails-forge/grails-forge-core/src/main/resources/assets/images/documentation.svg
@@ -5,9 +5,9 @@
 	 width="93.58px" height="93.58px" viewBox="0 0 93.58 93.58" enable-background="new 0 0 93.58 93.58" xml:space="preserve">
 <g>
 	<g>
-		<circle fill="none" stroke="#FEB672" stroke-width="2.8347" stroke-miterlimit="10" cx="46.88" cy="46.792" r="45.374"/>
+		<circle fill="#FEB672" stroke="#FEB672" stroke-width="2.8347" stroke-miterlimit="10" cx="46.88" cy="46.792" r="45.374"/>
 	</g>
-	<path fill="#FEB672" d="M64.379,40.958v24.062c0,1.208-0.979,2.188-2.188,2.188H31.567c-1.208,0-2.188-0.979-2.188-2.188V28.562
+	<path fill="white" d="M64.379,40.958v24.062c0,1.208-0.979,2.188-2.188,2.188H31.567c-1.208,0-2.188-0.979-2.188-2.188V28.562
 		c0-1.208,0.98-2.188,2.188-2.188h18.229v12.396c0,1.208,0.979,2.188,2.188,2.188H64.379z M55.629,44.604
 		c0-0.41-0.318-0.729-0.729-0.729H38.858c-0.41,0-0.729,0.319-0.729,0.729v1.458c0,0.41,0.319,0.729,0.729,0.729H54.9
 		c0.41,0,0.729-0.319,0.729-0.729V44.604z M55.629,50.438c0-0.41-0.318-0.729-0.729-0.729H38.858c-0.41,0-0.729,0.319-0.729,0.729
diff --git a/grails-forge/grails-forge-core/src/main/resources/assets/images/favicon.ico b/grails-forge/grails-forge-core/src/main/resources/assets/images/favicon.ico
index 76e4b11fed..6e0e0ecff9 100644
Binary files a/grails-forge/grails-forge-core/src/main/resources/assets/images/favicon.ico and b/grails-forge/grails-forge-core/src/main/resources/assets/images/favicon.ico differ
diff --git a/grails-forge/grails-forge-core/src/main/resources/assets/images/groovy.svg b/grails-forge/grails-forge-core/src/main/resources/assets/images/groovy.svg
new file mode 100644
index 0000000000..4bb39ed848
--- /dev/null
+++ b/grails-forge/grails-forge-core/src/main/resources/assets/images/groovy.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg"><path d="M103.555 95.851L64 80.52 24.446 95.847l15.618-24.436L0 56.447l49.208.23L63.999 32.1l14.794 24.578L128 56.453l-40.065 14.96 15.62 24.438"/><path d="M98.204 91.48l-34.17-13.244-34.168 13.242 13.491-21.11-34.61-12.926 42.506.198L64.03 36.41l12.78 21.23 42.509-.194L84.71 70.37l13.493 21.11" fill="#619cbc"/><path d="M37.804 44.66c3.413 0 1.424 8.528.21 11.1-1.04 2.201-3.38 5.377-6.153 5.377-3.327 0-3.228-3.727-1.904-6.532. [...]
\ No newline at end of file
diff --git a/grails-forge/grails-forge-core/src/main/resources/assets/images/java.svg b/grails-forge/grails-forge-core/src/main/resources/assets/images/java.svg
new file mode 100644
index 0000000000..051bf254ad
--- /dev/null
+++ b/grails-forge/grails-forge-core/src/main/resources/assets/images/java.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path fill="#0074BD" d="M47.617 98.12s-4.767 2.774 3.397 3.71c9.892 1.13 14.947.968 25.845-1.092 0 0 2.871 1.795 6.873 3.351-24.439 10.47-55.308-.607-36.115-5.969zm-2.988-13.665s-5.348 3.959 2.823 4.805c10.567 1.091 18.91 1.18 33.354-1.6 0 0 1.993 2.025 5.132 3.131-29.542 8.64-62.446.68-41.309-6.336z"/><path fill="#EA2D2E" d="M69.802 61.271c6.025 6.935-1.58 13.17-1.58 13.17s15.289-7.891 8.269-17.777c-6.559-9.215-11.587-13.792  [...]
\ No newline at end of file
diff --git a/grails-forge/grails-forge-core/src/main/resources/assets/images/spring-boot.svg b/grails-forge/grails-forge-core/src/main/resources/assets/images/spring-boot.svg
new file mode 100644
index 0000000000..d84ae1cf9f
--- /dev/null
+++ b/grails-forge/grails-forge-core/src/main/resources/assets/images/spring-boot.svg
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 24.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 510 457.8" style="enable-background:new 0 0 510 457.8;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#6DB33F;}
+</style>
+<title>icon-spring-boot</title>
+<g id="Layer_2_1_">
+	<g id="Layer_1-2">
+		<path class="st0" d="M503.5,201.4L403,27.5C394.3,12.4,372.9,0,355.4,0H154.6c-17.4,0-38.9,12.4-47.6,27.5L6.6,201.4
+			c-8.7,15.1-8.7,39.8,0,54.9l100.4,174c8.7,15.1,30.1,27.5,47.6,27.5h200.9c17.4,0,38.8-12.4,47.6-27.5l100.4-174
+			C512.2,241.2,512.2,216.5,503.5,201.4z M233.3,96.2c0-11.4,9.3-20.7,20.7-20.7c11.4,0,20.7,9.3,20.7,20.7v123.7
+			c0,11.4-9.3,20.7-20.7,20.7c-11.4,0-20.7-9.3-20.7-20.7l0,0V96.2z M254,360.3c-77.4,0-140.4-63-140.4-140.4
+			c0.1-44.4,21.1-86.1,56.7-112.7c8.2-6.1,19.7-4.4,25.8,3.8s4.4,19.7-3.8,25.8l0,0c-45.9,34.1-55.5,99-21.4,144.9
+			s99,55.5,144.9,21.4c26.3-19.5,41.8-50.4,41.8-83.2c-0.1-32.9-15.7-63.8-42.2-83.4c-8.2-6-9.9-17.6-3.9-25.8s17.6-9.9,25.8-3.9
+			c35.9,26.5,57,68.5,57.1,113.1C394.4,297.4,331.4,360.3,254,360.3z"/>
+	</g>
+</g>
+</svg>
diff --git a/grails-forge/grails-forge-core/src/main/resources/assets/images/spring.svg b/grails-forge/grails-forge-core/src/main/resources/assets/images/spring.svg
new file mode 100644
index 0000000000..93d6bde281
--- /dev/null
+++ b/grails-forge/grails-forge-core/src/main/resources/assets/images/spring.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 97.1 97"><defs><style>.cls-1{fill:#6db33f;}</style></defs><title>spring-icon</title><g id="Layer_2" data-name="Layer 2"><g id="logos"><path class="cls-1" d="M88.4,5.6a42.32,42.32,0,0,1-5.2,9.1A48.46,48.46,0,1,0,15.5,84l1.8,1.6A48.41,48.41,0,0,0,96.8,52C98.2,39.8,94.5,24.2,88.4,5.6ZM22.5,84.4a4.12,4.12,0,1,1-.6-5.8A4.21,4.21,0,0,1,22.5,84.4ZM88.1,69.9C76.2,85.8,50.6,80.4,34.3,81.2c0,0-2.9.2-5.8.6,0,0,1.1-.5,2.5-1,11.5-4,16.9-4.8,23.9-8. [...]
\ No newline at end of file
diff --git a/grails-forge/grails-forge-core/src/main/resources/assets/javascripts/welcome.js b/grails-forge/grails-forge-core/src/main/resources/assets/javascripts/welcome.js
new file mode 100644
index 0000000000..5b240fcb93
--- /dev/null
+++ b/grails-forge/grails-forge-core/src/main/resources/assets/javascripts/welcome.js
@@ -0,0 +1,116 @@
+(function () {
+    function compare(a, b) {
+        return a < b ? -1 : a > b ? 1 : 0;
+    }
+
+    function parseVersionParts(v) {
+        const cleaned = String(v || '').trim().replace(/[^0-9.]+/g, '.');
+        if (!cleaned) return [];
+        return cleaned.split('.').filter(Boolean).map((s) => Number(s) || 0);
+    }
+
+    function compareVersions(a, b) {
+        const pa = parseVersionParts(a);
+        const pb = parseVersionParts(b);
+        const n = Math.max(pa.length, pb.length);
+        for (let i = 0; i < n; i++) {
+            const da = pa[i] ?? 0;
+            const db = pb[i] ?? 0;
+            if (da !== db) return da < db ? -1 : 1;
+        }
+        const sa = String(a || '').toLowerCase();
+        const sb = String(b || '').toLowerCase();
+        return compare(sa, sb);
+    }
+
+    function getValue(tr, key) {
+        if (key === 'name') return (tr.dataset.name || '').toLowerCase();
+        if (key === 'version') return (tr.dataset.version || '');
+        if (key === 'order') return Number(tr.dataset.order || '0');
+        return '';
+    }
+
+    function sortTableBy(table, key, dir) {
+        const tbody = table.tBodies[0];
+        if (!tbody) return;
+
+        const rows = Array.from(tbody.querySelectorAll('tr'));
+        rows.sort((ra, rb) => {
+            if (key === 'version') {
+                return compareVersions(getValue(ra, key), getValue(rb, key)) * dir;
+            }
+            return compare(getValue(ra, key), getValue(rb, key)) * dir;
+        });
+
+        const frag = document.createDocumentFragment();
+        rows.forEach((r) => frag.appendChild(r));
+        tbody.appendChild(frag);
+    }
+
+    function setSortIndicator(table, activeTh, ariaSortValue, dataSortDirValue) {
+        table.querySelectorAll('th.sortable, th[data-sort-key]').forEach((th) => {
+            if (th !== activeTh) th.removeAttribute('data-sort-dir');
+        });
+
+        if (dataSortDirValue) {
+            activeTh.setAttribute('data-sort-dir', dataSortDirValue);
+        } else {
+            activeTh.removeAttribute('data-sort-dir');
+        }
+
+        table.querySelectorAll('th.sortable, th[data-sort-key]').forEach((th) => {
+            th.removeAttribute('aria-sort');
+        });
+        activeTh.setAttribute('aria-sort', ariaSortValue);
+    }
+
+    function initSortableTable(table) {
+        const headers = Array.from(
+            table.querySelectorAll('th.sortable[data-sort-key], th[data-sort-key]')
+        );
+        const state = { key: null, dir: 1 };
+
+        function activateSort(th) {
+            const key = th.getAttribute('data-sort-key');
+            if (!key) return;
+
+            state.dir = state.key === key ? state.dir * -1 : 1;
+            state.key = key;
+
+            const ariaSort = state.dir === 1 ? 'ascending' : 'descending';
+            const dataSortDir = state.dir === 1 ? 'asc' : 'desc';
+
+            setSortIndicator(table, th, ariaSort, dataSortDir);
+            sortTableBy(table, key, state.dir);
+        }
+
+        headers.forEach((th) => {
+            th.style.cursor = 'pointer';
+
+            th.addEventListener('click', () => activateSort(th));
+
+            // Keyboard support: Enter / Space should sort like a click.
+            th.addEventListener('keydown', (e) => {
+                const k = e.key;
+                if (k === 'Enter' || k === ' ' || k === 'Spacebar') {
+                    e.preventDefault();
+                    activateSort(th);
+                }
+            });
+        });
+
+        // Initial load: show ascending indicator on Name (and sort to match).
+        const defaultKey = 'name';
+        const defaultTh = headers.find((th) => (th.getAttribute('data-sort-key') || '') === defaultKey);
+        if (defaultTh) {
+            state.key = defaultKey;
+            state.dir = 1;
+            setSortIndicator(table, defaultTh, 'ascending', 'asc');
+            sortTableBy(table, defaultKey, state.dir);
+        }
+    }
+
+    document.addEventListener('DOMContentLoaded', () => {
+        document.querySelectorAll('table[data-sortable]').forEach(initSortableTable);
+    });
+})();
\ No newline at end of file
diff --git a/grails-forge/grails-forge-core/src/main/resources/assets/stylesheets/welcome.css b/grails-forge/grails-forge-core/src/main/resources/assets/stylesheets/welcome.css
new file mode 100644
index 0000000000..ba0a393921
--- /dev/null
+++ b/grails-forge/grails-forge-core/src/main/resources/assets/stylesheets/welcome.css
@@ -0,0 +1,63 @@
+.reload-indicator {
+    display: inline-flex;
+    align-items: center;
+    gap: .5rem;
+    padding: .35rem .6rem;
+    border: 1px solid var(--bs-border-color);
+    border-radius: 999px;
+    background: var(--bs-body-bg);
+    font-size: .875rem;
+    line-height: 1;
+    white-space: nowrap;
+}
+
+.reload-dot {
+    position: relative;
+    width: .6rem;
+    height: .6rem;
+    border-radius: 999px;
+    background: currentColor;
+    flex: 0 0 auto;
+}
+
+.reload-dot.ping::before {
+    content: "";
+    position: absolute;
+    inset: 0;
+    border-radius: 999px;
+    background: currentColor;
+    opacity: .35;
+    animation: reload-ping 1.25s ease-out infinite;
+}
+
+.sortable {
+    cursor: pointer;
+    user-select: none;
+}
+
+.sort-hint {
+    display: inline-block;
+    width: .9em;
+}
+
+.sort-hint::before {
+    content: '';
+}
+
+th.sortable[data-sort-dir="asc"] .sort-hint::before {
+    content: '↑';
+}
+
+th.sortable[data-sort-dir="desc"] .sort-hint::before {
+    content: '↓';
+}
+
+@keyframes reload-ping {
+    0% { transform: scale(1); opacity: .35; }
+    70% { transform: scale(2.25); opacity: 0; }
+    100% { transform: scale(2.25); opacity: 0; }
+}
+
+@media (prefers-reduced-motion: reduce) {
+    .reload-dot::before { animation: none; }
+}
diff --git a/grails-forge/grails-forge-core/src/main/resources/gsp/index.gsp b/grails-forge/grails-forge-core/src/main/resources/gsp/index.gsp
index c7544050a7..3581e0e031 100644
--- a/grails-forge/grails-forge-core/src/main/resources/gsp/index.gsp
+++ b/grails-forge/grails-forge-core/src/main/resources/gsp/index.gsp
@@ -1,84 +1,343 @@
-<%@ page import="grails.util.Environment; org.springframework.core.SpringVersion; org.springframework.boot.SpringBootVersion"
-%><!doctype html>
+<%@ page import="grails.util.Environment"%>
+<%@ page import="org.springframework.boot.SpringBootVersion"%>
+<%@ page import="org.springframework.core.SpringVersion"%>
+<g:set var="pluginManager" bean="pluginManager"/>
+<g:set var="servletContext" bean="servletContext"/>
+<g:set var="pluginsWithOrder"
+       value="${pluginManager.allPlugins.toList()
+               .withIndex()
+               .collect { p, i -> [plugin: p, order: i + 1] }
+               .sort { a, b -> a.plugin.name.toLowerCase() <=> b.plugin.name.toLowerCase() }}"
+/>
+<g:set var="numControllers" value="${grailsApplication.controllerClasses.size()}"/>
+<!doctype html>
 <html>
 <head>
-    <meta name="layout" content="main"/>
     <title>Welcome to Grails</title>
+    <meta name="layout" content="main"/>
+    <asset:stylesheet src="welcome.css"/>
 </head>
 <body>
-<content tag="nav">
-    <li class="nav-item dropdown">
-        <a href="#" class="nav-link dropdown-toggle" role="button" data-bs-toggle="dropdown" aria-expanded="false">Application Status <span class="caret"></span></a>
-        <ul class="dropdown-menu">
-            <li><a class="dropdown-item" href="#">Server: ${request.getServletContext().getServerInfo()}</a></li>
-            <li><a class="dropdown-item" href="#">Host: ${InetAddress.getLocalHost()}</a></li>
-            <li><a class="dropdown-item" href="#">Environment: ${Environment.current.name}</a></li>
-            <li><a class="dropdown-item" href="#">App version:
-                <g:meta name="info.app.version"/></a>
-            </li>
-            <li><a class="dropdown-item" href="#">App profile: ${grailsApplication.config.getProperty('grails.profile')}</a></li>
-            <li><hr class="dropdown-divider"></li>
-            <li><a class="dropdown-item" href="#">Grails version:
-                <g:meta name="info.app.grailsVersion"/></a>
-            </li>
-            <li><a class="dropdown-item" href="#">Groovy version: ${GroovySystem.getVersion()}</a></li>
-            <li><a class="dropdown-item" href="#">JVM version: ${System.getProperty('java.version')}</a></li>
-            <li><a class="dropdown-item" href="#">Spring Boot version: ${SpringBootVersion.getVersion()}</a></li>
-            <li><a class="dropdown-item" href="#">Spring version: ${SpringVersion.getVersion()}</a></li>
-            <li><hr class="dropdown-divider"></li>
-            <li><a class="dropdown-item" href="#">Reloading active: ${Environment.reloadingAgentEnabled}</a></li>
-        </ul>
-    </li>
-    <li class="nav-item dropdown">
-        <a href="#" class="nav-link dropdown-toggle" role="button" data-bs-toggle="dropdown" aria-expanded="false">Artefacts <span class="caret"></span></a>
-        <ul class="dropdown-menu">
-            <li><a class="dropdown-item" href="#">Controllers: ${grailsApplication.controllerClasses.size()}</a></li>
-            <li><a class="dropdown-item" href="#">Domains: ${grailsApplication.domainClasses.size()}</a></li>
-            <li><a class="dropdown-item" href="#">Services: ${grailsApplication.serviceClasses.size()}</a></li>
-            <li><a class="dropdown-item" href="#">Tag Libraries: ${grailsApplication.tagLibClasses.size()}</a></li>
-        </ul>
-    </li>
-    <li class="nav-item dropdown">
-        <a href="#" class="nav-link dropdown-toggle" role="button" data-bs-toggle="dropdown" aria-expanded="false">Installed Plugins<span class="caret"></span></a>
-        <ul class="dropdown-menu dropdown-menu-right">
-            <g:each var="plugin" in="${applicationContext.getBean('pluginManager').allPlugins}">
-                <li><a class="dropdown-item" href="#">${plugin.name} - ${plugin.version}</a></li>
-            </g:each>
-        </ul>
-    </li>
-</content>
+<main id="content" role="main" class="pb-4 pb-md-5">
+    <div class="container-lg py-2 py-md-3">
+        <div class="row align-items-top g-4">
 
-<div class="svg" role="presentation">
-    <div class="bg-dark-subtle text-center">
-        <asset:image src="grails-cupsonly-logo-white.svg" class="w-50"/>
+            <%-- WELCOME MESSAGE --%>
+            <div class="col-12 col-md-7">
+                <h1 class="display-6 fw-semibold mb-2">Welcome to Grails</h1>
+                <p class="lead text-body-secondary">
+                    Congratulations, you have successfully started a Grails application.
+                </p>
+                <p class="text-body-secondary">
+                    At the moment this is the default page, feel free to modify it to either
+                    redirect to a controller or display whatever content you may choose.
+                </p>
+            </div>
+
+            <%-- RUNTIME VERSIONS --%>
+            <div class="col-12 col-md-5">
+                <div class="card border-1 shadow-sm">
+                    <div class="card-body">
+                        <h6 class="card-title mb-3 fw-semibold">Runtime versions</h6>
+                        <ul class="list-group list-group-flush small">
+                            <li class="list-group-item d-flex justify-content-between align-items-center px-0">
+                                <span class="d-inline-flex align-items-center text-body-secondary">
+                                    <asset:image src="grails.svg" alt="Grails" width="18" height="18" class="me-2"/>
+                                    Grails
+                                </span>
+                                <g:meta name="info.app.grailsVersion"/>
+                            </li>
+                            <li class="list-group-item d-flex justify-content-between align-items-center px-0">
+                                <span class="d-inline-flex align-items-center text-body-secondary">
+                                    <asset:image src="spring-boot.svg" alt="Spring Boot" width="18" height="18" class="me-2"/>
+                                    Spring Boot
+                                </span>
+                                ${SpringBootVersion.getVersion()}
+                            </li>
+                            <li class="list-group-item d-flex justify-content-between align-items-center px-0">
+                                <span class="d-inline-flex align-items-center text-body-secondary">
+                                    <asset:image src="spring.svg" alt="Spring" width="18" height="18" class="me-2"/>
+                                    Spring
+                                </span>
+                                ${SpringVersion.getVersion()}
+                            </li>
+                            <li class="list-group-item d-flex justify-content-between align-items-center px-0">
+                                <span class="d-inline-flex align-items-center text-body-secondary">
+                                    <asset:image src="groovy.svg" alt="Groovy" width="18" height="18" class="me-2"/>
+                                    Groovy
+                                </span>
+                                ${GroovySystem.getVersion()}
+                            </li>
+                            <li class="list-group-item d-flex justify-content-between align-items-center px-0">
+                                <span class="d-inline-flex align-items-center text-body-secondary">
+                                    <asset:image src="java.svg" alt="Java" width="18" height="18" class="me-2"/>
+                                    JVM (${System.getProperty('java.vendor')})
+                                </span>
+                                ${System.getProperty('java.version')}
+                            </li>
+                        </ul>
+                    </div>
+                </div>
+            </div>
+        </div>
     </div>
-</div>
+    <div class="container-lg">
+        <div class="row g-4 align-items-stretch">
 
-<div id="content" role="main">
-    <div class="container">
-        <section class="row colset-2-its">
-            <h1>Welcome to Grails</h1>
+            <%-- APPLICATION INFO --%>
+            <div class="col-12 col-lg-4">
+                <div class="card border-1 shadow-sm h-100">
+                    <div class="card-body">
+                        <div class="d-flex align-items-center justify-content-between mb-3">
+                            <h6 class="card-title mb-0 fw-semibold">Application</h6>
+                            <g:if test="${Environment.reloadingAgentEnabled}">
+                                <span class="reload-indicator text-success" role="status" aria-label="Reloading active">
+                                    <span class="reload-dot ping" aria-hidden="true"></span>
+                                    <span class="text-body-secondary">Reloading active</span>
+                                </span>
+                            </g:if>
+                            <g:else>
+                                <span class="reload-indicator text-danger" role="status" aria-label="Reloading inactive">
+                                    <span class="reload-dot" aria-hidden="true"></span>
+                                    <span class="text-body-secondary">Reloading inactive</span>
+                                </span>
+                            </g:else>
+                        </div>
+                        <ul class="list-group list-group-flush small">
+                            <li class="list-group-item d-flex justify-content-between align-items-center px-0">
+                                <span class="text-body-secondary">Name</span>
+                                <span class="fw-medium text-truncate ms-3"><g:meta name="info.app.name"/></span>
+                            </li>
+                            <li class="list-group-item d-flex justify-content-between align-items-center px-0">
+                                <span class="text-body-secondary">Version</span>
+                                <span class="fw-medium" style="font-variant-numeric: tabular-nums;">
+                                    <g:meta name="info.app.version"/>
+                                </span>
+                            </li>
+                            <li class="list-group-item d-flex justify-content-between align-items-center px-0">
+                                <span class="text-body-secondary">Profile</span>
+                                <span class="fw-medium text-truncate ms-3">
+                                    ${grailsApplication.config.getProperty('grails.profile')}
+                                </span>
+                            </li>
+                            <li class="list-group-item d-flex justify-content-between align-items-center px-0">
+                                <span class="text-body-secondary">Environment</span>
+                                <span class="fw-medium">${Environment.current.name}</span>
+                            </li>
+                        </ul>
+                    </div>
+                </div>
+            </div>
+
+            <%-- SERVER INFO --%>
+            <div class="col-12 col-lg-4">
+                <div class="card border-1 shadow-sm h-100">
+                    <div class="card-body">
+                        <div class="d-flex align-items-center justify-content-between mb-3">
+                            <h6 class="card-title mb-0 fw-semibold">Server</h6>
+                        </div>
+                        <ul class="list-group list-group-flush small">
+                            <li class="list-group-item d-flex justify-content-between align-items-center px-0">
+                                <span class="text-body-secondary">Servlet Container</span>
+                                <span class="fw-medium text-truncate ms-3">${servletContext.serverInfo}</span>
+                            </li>
+                            <li class="list-group-item d-flex justify-content-between align-items-center px-0">
+                                <span class="text-body-secondary">Host</span>
+                                <span class="fw-medium text-truncate ms-3">${InetAddress.localHost}</span>
+                            </li>
+                            <li class="list-group-item d-flex justify-content-between align-items-center px-0">
+                                <span class="text-body-secondary">OS</span>
+                                <span class="fw-medium text-truncate ms-3">
+                                    ${System.getProperty('os.name')} ${System.getProperty('os.version')} (${System.getProperty('os.arch')})
+                                </span>
+                            </li>
+                        </ul>
+                    </div>
+                </div>
+            </div>
 
-            <p>
-                Congratulations, you have successfully started your first Grails application! At the moment
-                this is the default page, feel free to modify it to either redirect to a controller or display
-                whatever content you may choose. Below is a list of controllers that are currently deployed in
-                this application, click on each to execute its default action:
-            </p>
+            <%-- ARTEFACT COUNTS --%>
+            <div class="col-12 col-lg-4">
+                <div class="card border-1 shadow-sm h-100">
+                    <div class="card-body">
+                        <div class="d-flex align-items-center justify-content-between mb-3">
+                            <h6 class="card-title mb-0 fw-semibold">Artefact counts</h6>
+                        </div>
 
-            <div id="controllers" role="navigation">
-                <h2>Available Controllers:</h2>
-                <ul>
-                    <g:each var="c" in="${grailsApplication.controllerClasses.sort { it.fullName } }">
-                        <li class="controller">
-                            <g:link controller="${c.logicalPropertyName}">${c.fullName}</g:link>
-                        </li>
-                    </g:each>
-                </ul>
+                        <ul class="list-group list-group-flush small">
+                            <li class="list-group-item d-flex justify-content-between align-items-center px-0">
+                                <span class="text-body-secondary">Controllers</span>
+                                <span class="fw-medium">${numControllers}</span>
+                            </li>
+                            <li class="list-group-item d-flex justify-content-between align-items-center px-0">
+                                <span class="text-body-secondary">Domains</span>
+                                <span class="fw-medium">${grailsApplication.domainClasses.size()}</span>
+                            </li>
+                            <li class="list-group-item d-flex justify-content-between align-items-center px-0">
+                                <span class="text-body-secondary">Services</span>
+                                <span class="fw-medium">${grailsApplication.serviceClasses.size()}</span>
+                            </li>
+                            <li class="list-group-item d-flex justify-content-between align-items-center px-0">
+                                <span class="text-body-secondary">Tag Libraries</span>
+                                <span class="fw-medium">${grailsApplication.tagLibClasses.size()}</span>
+                            </li>
+                        </ul>
+                    </div>
+                </div>
             </div>
-        </section>
+
+        </div>
     </div>
-</div>
 
+    <%-- AVAILABLE CONTROLLERS --%>
+    <div class="container-lg mt-4">
+        <div class="row g-4 align-items-start">
+            <div class="col-12 col-lg-7">
+                <div class="card border-1 shadow-sm h-100">
+                    <div class="card-body p-4 p-md-5">
+                        <div class="d-flex flex-column flex-md-row align-items-md-center justify-content-between gap-2">
+                            <div>
+                                <h2 class="h4 mb-1">Available Controllers</h2>
+                                <p class="text-body-secondary mb-0">
+                                    ${numControllers} controller${numControllers != 1 ? 's' : ''} detected.
+                                </p>
+                            </div>
+                            <g:if test="${numControllers != 0}">
+                                <div class="small text-body-secondary">
+                                    Click a controller to execute its default action.
+                                </div>
+                            </g:if>
+                        </div>
+                        <hr class="my-3 my-md-4 text-body-tertiary"/>
+                        <g:set var="controllersByNamespace"
+                               value="${grailsApplication.controllerClasses
+                                       .groupBy { cc -> ((cc.namespace ?: '').trim()) ?: 'default' }
+                                       .sort { a, b -> a.key.toString().toLowerCase() <=> b.key.toString().toLowerCase() }}"/>
+
+                        <g:each var="nsEntry" in="${controllersByNamespace}" status="nsIndex">
+                            <div class="${nsIndex > 0 ? 'mt-4' : ''}">
+                                <div class="px-0 py-2 bg-body-tertiary">
+                                    <div class="d-flex align-items-center justify-content-between">
+                                        <div class="small text-uppercase text-body-secondary fw-semibold"
+                                             style="letter-spacing: .04em;">
+                                            <g:if test="${nsEntry.key != 'default'}">
+                                                ${nsEntry.key}
+                                            </g:if>
+                                            <g:else>
+                                                Default namespace
+                                            </g:else>
+                                        </div>
+                                    </div>
+                                </div>
+
+                                <ul class="list-group list-group-flush">
+                                    <g:each var="c" in="${nsEntry.value.sort { it.fullName }}">
+                                        <g:set var="simpleName" value="${(c.fullName ?: '')
+                                                .tokenize('.')
+                                                .last()
+                                                .replaceFirst(/Controller$/, '')}"/>
+
+                                        <g:set var="controllerUrl"
+                                               value="${createLink(controller: c.logicalPropertyName, namespace: c.namespace)}"/>
+
+                                        <li class="list-group-item px-0">
+                                            <div class="d-flex align-items-center justify-content-between gap-3">
+                                                <g:link controller="${c.logicalPropertyName}"
+                                                        namespace="${c.namespace}"
+                                                        class="d-flex align-items-center gap-3 text-decoration-none min-w-0 flex-grow-1">
+                                                    <div class="min-w-0">
+                                                        <div class="fw-semibold text-body text-truncate">
+                                                            ${simpleName}
+                                                        </div>
+                                                    </div>
+                                                </g:link>
+
+                                                <a href="${controllerUrl}"
+                                                   class="small link-primary link-offset-2 link-underline-opacity-0 link-underline-opacity-75-hover flex-shrink-0">
+                                                    ${controllerUrl}
+                                                </a>
+                                            </div>
+                                        </li>
+                                    </g:each>
+                                </ul>
+                            </div>
+                        </g:each>
+                    </div>
+                </div>
+            </div>
+
+            <%-- PLUGINS --%>
+            <div class="col-12 col-lg-5">
+                <div class="card border-1 shadow-sm h-100">
+                    <div class="card-body">
+                        <div class="d-flex align-items-center justify-content-between mb-3">
+                            <h6 class="card-title mb-0 fw-semibold">Installed plugins</h6>
+                            <span class="badge text-bg-light border">
+                                ${pluginManager.allPlugins.size()}
+                            </span>
+                        </div>
+
+                        <div class="table-responsive">
+                            <table class="table table-sm table-striped table-hover" data-sortable="true">
+                                <thead class="table-light small">
+                                <tr>
+                                    <th scope="col"
+                                        class="text-body-secondary ps-0 fw-semibold sortable"
+                                        data-sort-key="name"
+                                        role="button"
+                                        tabindex="0"
+                                        aria-label="Sort by name">
+                                        Name <span class="sort-hint" aria-hidden="true"></span>
+                                    </th>
+                                    <th scope="col"
+                                        class="text-body-secondary ps-0 fw-semibold text-end sortable"
+                                        data-sort-key="version"
+                                        role="button"
+                                        tabindex="0"
+                                        aria-label="Sort by version">
+                                        <span class="sort-hint" aria-hidden="true"></span> Version
+                                    </th>
+                                    <th scope="col"
+                                        class="text-body-secondary text-end pe-0 sortable"
+                                        data-sort-key="order"
+                                        role="button"
+                                        tabindex="0"
+                                        aria-label="Sort by load order">
+                                        <span class="sort-hint" aria-hidden="true"></span> Load order
+                                    </th>
+                                </tr>
+                                </thead>
+                                <tbody class="small">
+                                <g:each var="row" in="${pluginsWithOrder}">
+                                    <g:set var="pluginName"
+                                           value="${row.plugin.name
+                                                   .replaceAll(/([A-Z]+)([A-Z][a-z])/, '$1 $2')
+                                                   .replaceAll(/([a-z0-9])([A-Z])/, '$1 $2')
+                                                   .replaceAll(/[_-]+/, ' ')
+                                                   .trim()
+                                                   .capitalize()}"
+                                    />
+                                    <tr data-name="${pluginName}" data-version="${row.plugin.version}" data-order="${row.order}">
+                                        <td class="text-truncate">
+                                            ${pluginName}
+                                        </td>
+                                        <td class="text-end" style="font-variant-numeric: tabular-nums;">
+                                            ${row.plugin.version}
+                                        </td>
+                                        <td class="text-end text-body-secondary" style="font-variant-numeric: tabular-nums;">
+                                            ${row.order}
+                                        </td>
+                                    </tr>
+                                </g:each>
+                                </tbody>
+                            </table>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</main>
+<asset:javascript src="welcome.js"/>
 </body>
 </html>
diff --git a/grails-forge/grails-forge-core/src/main/resources/gsp/main.gsp b/grails-forge/grails-forge-core/src/main/resources/gsp/main.gsp
index 6f09bcc995..023449b955 100644
--- a/grails-forge/grails-forge-core/src/main/resources/gsp/main.gsp
+++ b/grails-forge/grails-forge-core/src/main/resources/gsp/main.gsp
@@ -1,12 +1,9 @@
 <!doctype html>
-<html lang="en" class="no-js">
+<html lang="en">
 <head>
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
-    <title>
-    <g:layoutTitle default="Grails"/>
-    </title>
+    <meta charset="utf-8"/>
     <meta name="viewport" content="width=device-width, initial-scale=1"/>
+    <title><g:layoutTitle default="Grails"/></title>
     <asset:link rel="icon" href="favicon.ico" type="image/x-ico"/>
     <asset:stylesheet src="application.css"/>
     <g:layoutHead/>
@@ -14,68 +11,76 @@
 
 <body>
 
-<nav class="navbar navbar-expand-lg bg-body-tertiary">
-    <div class="container-fluid">
-        <a class="navbar-brand" href="/#"><asset:image class="w-75" src="grails.svg" alt="Grails Logo"/></a>
-        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
-            <span class="navbar-toggler-icon"></span>
-        </button>
-
-        <div class="collapse navbar-collapse" aria-expanded="false" id="navbarContent">
-            <ul class="navbar-nav">
-                <g:pageProperty name="page.nav"/>
-            </ul>
-        </div>
+<nav class="navbar navbar-expand-lg bg-body border-bottom shadow-sm">
+    <div class="container-lg">
+        <a class="navbar-brand d-flex align-items-center" href="${request.contextPath}/">
+            <asset:image class="w-75" src="grails.svg" alt="Grails Logo"/>
+        </a>
     </div>
 </nav>
 
-<g:layoutBody/>
+<div class="bg-body-tertiary">
+    <div class="container-lg py-4">
+        <g:layoutBody/>
+    </div>
+</div>
 
-<div class="footer" role="contentinfo">
-    <div class="container-fluid">
-        <div class="row">
-            <div class="card border-0 col-12 col-md">
-                <div class="card-body">
-                    <h6 class="card-title">
-                        <a class="link-underline link-underline-opacity-0" href="https://guides.grails.org" target="_blank">
-                            <asset:image src="advancedgrails.svg" alt="Grails Guides" class="me-2" width="34" />Grails Guides
-                        </a>
-                    </h6>
-                    <p class="card-text">Building your first Grails app? Looking to add security, or create a Single-Page-App? Check out the <a href="https://guides.grails.org" target="_blank">Grails Guides</a> for step-by-step tutorials.</p>
-                </div>
+<footer class="border-top py-5" role="contentinfo">
+    <div class="container-lg">
+        <div class="row g-4">
+            <div class="col-12 col-md-4">
+                <a class="card h-100 text-decoration-none shadow-sm border-1"
+                   href="https://guides.grails.org" target="_blank" rel="noopener">
+                    <div class="card-body p-4">
+                        <div class="d-flex align-items-center justify-content-between mb-2">
+                            <h6 class="card-title mb-0 fw-semibold">Grails Guides</h6>
+                            <asset:image src="advancedgrails.svg" alt="Grails Guides" width="34" height="34"/>
+                        </div>
+                        <p class="card-text text-body-secondary mb-0">
+                            Building your first Grails app? Looking to add security, or create a Single-Page-App?
+                            Check out the Grails Guides for step-by-step tutorials.
+                        </p>
+                    </div>
+                </a>
             </div>
-            <div class="card border-0 col-12 col-md">
-                <div class="card-body">
-                    <h6 class="card-title">
-                        <a class="link-underline link-underline-opacity-0" href="https://grails.apache.org/docs/" target="_blank">
-                            <asset:image src="documentation.svg" alt="Grails Documentation" class="me-2" width="34" />Documentation
-                        </a>
-                    </h6>
-                    <p class="card-text">Ready to dig in? You can find in-depth documentation for all the features of Grails in the <a href="https://grails.apache.org/docs/" target="_blank">User Guide</a>.</p>
-                </div>
+            <div class="col-12 col-md-4">
+                <a class="card h-100 text-decoration-none shadow-sm border-1"
+                   href="https://grails.apache.org/docs/" target="_blank" rel="noopener">
+                    <div class="card-body p-4">
+                        <div class="d-flex align-items-center justify-content-between mb-2">
+                            <h6 class="card-title mb-0 fw-semibold">Documentation</h6>
+                            <asset:image src="documentation.svg" alt="Grails Documentation" width="34" height="34"/>
+                        </div>
+                        <p class="card-text text-body-secondary mb-0">
+                            Ready to dig in? You can find in-depth documentation for all the features
+                            of Grails in the User Guide.
+                        </p>
+                    </div>
+                </a>
             </div>
-            <div class="card border-0 col-12 col-md">
-                <div class="card-body">
-                    <h6 class="card-title">
-                        <a class="link-underline link-underline-opacity-0" href="https://slack.grails.org" target="_blank">
-                            <asset:image src="slack.svg" alt="Grails Slack" class="me-2" width="34" />Join the Community
-                        </a>
-                    </h6>
-                    <p class="card-text">Get feedback and share your experience with other Grails developers in the community <a href="https://slack.grails.org" target="_blank">Slack channel</a>.</p>
-                </div>
+            <div class="col-12 col-md-4">
+                <a class="card h-100 text-decoration-none shadow-sm border-1"
+                   href="https://slack.grails.org" target="_blank" rel="noopener">
+                    <div class="card-body p-4">
+                        <div class="d-flex align-items-center justify-content-between mb-2">
+                            <h6 class="card-title mb-0 fw-semibold">Join the Community</h6>
+                            <asset:image src="slack.svg" alt="Grails Slack" width="34" height="34"/>
+                        </div>
+                        <p class="card-text text-body-secondary mb-0">
+                            Get feedback and share your experience with other Grails developers
+                            in the community Slack channel.
+                        </p>
+                    </div>
+                </a>
             </div>
         </div>
     </div>
-</div>
-
+</footer>
 <div id="spinner" class="position-absolute top-0 end-0 p-1" style="display:none;">
     <div class="spinner-border spinner-border-sm" role="status">
         <span class="visually-hidden">Loading...</span>
     </div>
 </div>
-
-
 <asset:javascript src="application.js"/>
-
 </body>
 </html>
diff --git a/grails-profiles/web/skeleton/grails-app/assets/images/advancedgrails.svg b/grails-profiles/web/skeleton/grails-app/assets/images/advancedgrails.svg
index 8b63ec8be5..9036e3cf5f 100644
--- a/grails-profiles/web/skeleton/grails-app/assets/images/advancedgrails.svg
+++ b/grails-profiles/web/skeleton/grails-app/assets/images/advancedgrails.svg
@@ -5,14 +5,14 @@
 	 width="93.58px" height="93.58px" viewBox="0 0 93.58 93.58" enable-background="new 0 0 93.58 93.58" xml:space="preserve">
 <g>
 	<g>
-		<circle fill="none" stroke="#FEB672" stroke-width="2.8347" stroke-miterlimit="10" cx="46.79" cy="46.789" r="45.374"/>
+		<circle fill="#FEB672" stroke="#FEB672" stroke-width="2.8347" stroke-miterlimit="10" cx="46.79" cy="46.789" r="45.374"/>
 	</g>
 	<g>
-		<path fill="#FEB672" d="M71.126,29.576c0,0.414-0.337,0.75-0.75,0.75h-3.25v3.25c0,0.415-0.337,0.751-0.751,0.751h-1.499
+		<path fill="white" d="M71.126,29.576c0,0.414-0.337,0.75-0.75,0.75h-3.25v3.25c0,0.415-0.337,0.751-0.751,0.751h-1.499
 			c-0.415,0-0.75-0.336-0.75-0.751v-3.25h-3.251c-0.414,0-0.749-0.336-0.749-0.75v-1.498c0-0.416,0.335-0.752,0.749-0.752h3.251
 			v-3.249c0-0.414,0.335-0.75,0.75-0.75h1.499c0.414,0,0.751,0.336,0.751,0.75v3.249h3.25c0.413,0,0.75,0.336,0.75,0.752V29.576z"/>
 	</g>
-	<path fill="#FEB672" d="M50.42,60.386c0.554,1.467,0.855,1.951,1.493,3.44c0.271,0.627,0.523,1.228,0.649,1.518
+	<path fill="white" d="M50.42,60.386c0.554,1.467,0.855,1.951,1.493,3.44c0.271,0.627,0.523,1.228,0.649,1.518
 		c0.049,0.117,0.036,0.248-0.033,0.355c-0.172,0.259-0.552,0.747-1.181,1.086c-1.098,0.594-3.409,0.809-4.555,0.812h-0.006
 		c-1.146-0.004-3.457-0.219-4.558-0.812c-0.627-0.339-1.006-0.827-1.177-1.086c-0.07-0.107-0.083-0.238-0.032-0.355
 		c0.123-0.29,0.376-0.891,0.646-1.518c0.64-1.489,0.941-1.974,1.495-3.44c0.485-1.294,0.729-3.175,0.745-4.593
diff --git a/grails-profiles/web/skeleton/grails-app/assets/images/documentation.svg b/grails-profiles/web/skeleton/grails-app/assets/images/documentation.svg
index 29bc9d57d3..78ff4dc4b9 100644
--- a/grails-profiles/web/skeleton/grails-app/assets/images/documentation.svg
+++ b/grails-profiles/web/skeleton/grails-app/assets/images/documentation.svg
@@ -5,9 +5,9 @@
 	 width="93.58px" height="93.58px" viewBox="0 0 93.58 93.58" enable-background="new 0 0 93.58 93.58" xml:space="preserve">
 <g>
 	<g>
-		<circle fill="none" stroke="#FEB672" stroke-width="2.8347" stroke-miterlimit="10" cx="46.88" cy="46.792" r="45.374"/>
+		<circle fill="#FEB672" stroke="#FEB672" stroke-width="2.8347" stroke-miterlimit="10" cx="46.88" cy="46.792" r="45.374"/>
 	</g>
-	<path fill="#FEB672" d="M64.379,40.958v24.062c0,1.208-0.979,2.188-2.188,2.188H31.567c-1.208,0-2.188-0.979-2.188-2.188V28.562
+	<path fill="white" d="M64.379,40.958v24.062c0,1.208-0.979,2.188-2.188,2.188H31.567c-1.208,0-2.188-0.979-2.188-2.188V28.562
 		c0-1.208,0.98-2.188,2.188-2.188h18.229v12.396c0,1.208,0.979,2.188,2.188,2.188H64.379z M55.629,44.604
 		c0-0.41-0.318-0.729-0.729-0.729H38.858c-0.41,0-0.729,0.319-0.729,0.729v1.458c0,0.41,0.319,0.729,0.729,0.729H54.9
 		c0.41,0,0.729-0.319,0.729-0.729V44.604z M55.629,50.438c0-0.41-0.318-0.729-0.729-0.729H38.858c-0.41,0-0.729,0.319-0.729,0.729
diff --git a/grails-profiles/web/skeleton/grails-app/assets/images/favicon.ico b/grails-profiles/web/skeleton/grails-app/assets/images/favicon.ico
index 76e4b11fed..6e0e0ecff9 100644
Binary files a/grails-profiles/web/skeleton/grails-app/assets/images/favicon.ico and b/grails-profiles/web/skeleton/grails-app/assets/images/favicon.ico differ
diff --git a/grails-profiles/web/skeleton/grails-app/assets/images/groovy.svg b/grails-profiles/web/skeleton/grails-app/assets/images/groovy.svg
new file mode 100644
index 0000000000..4bb39ed848
--- /dev/null
+++ b/grails-profiles/web/skeleton/grails-app/assets/images/groovy.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg"><path d="M103.555 95.851L64 80.52 24.446 95.847l15.618-24.436L0 56.447l49.208.23L63.999 32.1l14.794 24.578L128 56.453l-40.065 14.96 15.62 24.438"/><path d="M98.204 91.48l-34.17-13.244-34.168 13.242 13.491-21.11-34.61-12.926 42.506.198L64.03 36.41l12.78 21.23 42.509-.194L84.71 70.37l13.493 21.11" fill="#619cbc"/><path d="M37.804 44.66c3.413 0 1.424 8.528.21 11.1-1.04 2.201-3.38 5.377-6.153 5.377-3.327 0-3.228-3.727-1.904-6.532. [...]
\ No newline at end of file
diff --git a/grails-profiles/web/skeleton/grails-app/assets/images/java.svg b/grails-profiles/web/skeleton/grails-app/assets/images/java.svg
new file mode 100644
index 0000000000..051bf254ad
--- /dev/null
+++ b/grails-profiles/web/skeleton/grails-app/assets/images/java.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path fill="#0074BD" d="M47.617 98.12s-4.767 2.774 3.397 3.71c9.892 1.13 14.947.968 25.845-1.092 0 0 2.871 1.795 6.873 3.351-24.439 10.47-55.308-.607-36.115-5.969zm-2.988-13.665s-5.348 3.959 2.823 4.805c10.567 1.091 18.91 1.18 33.354-1.6 0 0 1.993 2.025 5.132 3.131-29.542 8.64-62.446.68-41.309-6.336z"/><path fill="#EA2D2E" d="M69.802 61.271c6.025 6.935-1.58 13.17-1.58 13.17s15.289-7.891 8.269-17.777c-6.559-9.215-11.587-13.792  [...]
\ No newline at end of file
diff --git a/grails-profiles/web/skeleton/grails-app/assets/images/spring-boot.svg b/grails-profiles/web/skeleton/grails-app/assets/images/spring-boot.svg
new file mode 100644
index 0000000000..d84ae1cf9f
--- /dev/null
+++ b/grails-profiles/web/skeleton/grails-app/assets/images/spring-boot.svg
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 24.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 510 457.8" style="enable-background:new 0 0 510 457.8;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#6DB33F;}
+</style>
+<title>icon-spring-boot</title>
+<g id="Layer_2_1_">
+	<g id="Layer_1-2">
+		<path class="st0" d="M503.5,201.4L403,27.5C394.3,12.4,372.9,0,355.4,0H154.6c-17.4,0-38.9,12.4-47.6,27.5L6.6,201.4
+			c-8.7,15.1-8.7,39.8,0,54.9l100.4,174c8.7,15.1,30.1,27.5,47.6,27.5h200.9c17.4,0,38.8-12.4,47.6-27.5l100.4-174
+			C512.2,241.2,512.2,216.5,503.5,201.4z M233.3,96.2c0-11.4,9.3-20.7,20.7-20.7c11.4,0,20.7,9.3,20.7,20.7v123.7
+			c0,11.4-9.3,20.7-20.7,20.7c-11.4,0-20.7-9.3-20.7-20.7l0,0V96.2z M254,360.3c-77.4,0-140.4-63-140.4-140.4
+			c0.1-44.4,21.1-86.1,56.7-112.7c8.2-6.1,19.7-4.4,25.8,3.8s4.4,19.7-3.8,25.8l0,0c-45.9,34.1-55.5,99-21.4,144.9
+			s99,55.5,144.9,21.4c26.3-19.5,41.8-50.4,41.8-83.2c-0.1-32.9-15.7-63.8-42.2-83.4c-8.2-6-9.9-17.6-3.9-25.8s17.6-9.9,25.8-3.9
+			c35.9,26.5,57,68.5,57.1,113.1C394.4,297.4,331.4,360.3,254,360.3z"/>
+	</g>
+</g>
+</svg>
diff --git a/grails-profiles/web/skeleton/grails-app/assets/images/spring.svg b/grails-profiles/web/skeleton/grails-app/assets/images/spring.svg
new file mode 100644
index 0000000000..93d6bde281
--- /dev/null
+++ b/grails-profiles/web/skeleton/grails-app/assets/images/spring.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 97.1 97"><defs><style>.cls-1{fill:#6db33f;}</style></defs><title>spring-icon</title><g id="Layer_2" data-name="Layer 2"><g id="logos"><path class="cls-1" d="M88.4,5.6a42.32,42.32,0,0,1-5.2,9.1A48.46,48.46,0,1,0,15.5,84l1.8,1.6A48.41,48.41,0,0,0,96.8,52C98.2,39.8,94.5,24.2,88.4,5.6ZM22.5,84.4a4.12,4.12,0,1,1-.6-5.8A4.21,4.21,0,0,1,22.5,84.4ZM88.1,69.9C76.2,85.8,50.6,80.4,34.3,81.2c0,0-2.9.2-5.8.6,0,0,1.1-.5,2.5-1,11.5-4,16.9-4.8,23.9-8. [...]
\ No newline at end of file
diff --git a/grails-profiles/web/skeleton/grails-app/assets/javascripts/welcome.js b/grails-profiles/web/skeleton/grails-app/assets/javascripts/welcome.js
new file mode 100644
index 0000000000..e2f6333006
--- /dev/null
+++ b/grails-profiles/web/skeleton/grails-app/assets/javascripts/welcome.js
@@ -0,0 +1,115 @@
+(function () {
+    function compare(a, b) {
+        return a < b ? -1 : a > b ? 1 : 0;
+    }
+
+    function parseVersionParts(v) {
+        const cleaned = String(v || '').trim().replace(/[^0-9.]+/g, '.');
+        if (!cleaned) return [];
+        return cleaned.split('.').filter(Boolean).map((s) => Number(s) || 0);
+    }
+
+    function compareVersions(a, b) {
+        const pa = parseVersionParts(a);
+        const pb = parseVersionParts(b);
+        const n = Math.max(pa.length, pb.length);
+        for (let i = 0; i < n; i++) {
+            const da = pa[i] ?? 0;
+            const db = pb[i] ?? 0;
+            if (da !== db) return da < db ? -1 : 1;
+        }
+        const sa = String(a || '').toLowerCase();
+        const sb = String(b || '').toLowerCase();
+        return compare(sa, sb);
+    }
+
+    function getValue(tr, key) {
+        if (key === 'name') return (tr.dataset.name || '').toLowerCase();
+        if (key === 'version') return (tr.dataset.version || '');
+        if (key === 'order') return Number(tr.dataset.order || '0');
+        return '';
+    }
+
+    function sortTableBy(table, key, dir) {
+        const tbody = table.tBodies[0];
+        if (!tbody) return;
+
+        const rows = Array.from(tbody.querySelectorAll('tr'));
+        rows.sort((ra, rb) => {
+            if (key === 'version') {
+                return compareVersions(getValue(ra, key), getValue(rb, key)) * dir;
+            }
+            return compare(getValue(ra, key), getValue(rb, key)) * dir;
+        });
+
+        const frag = document.createDocumentFragment();
+        rows.forEach((r) => frag.appendChild(r));
+        tbody.appendChild(frag);
+    }
+
+    function setSortIndicator(table, activeTh, ariaSortValue, dataSortDirValue) {
+        table.querySelectorAll('th.sortable, th[data-sort-key]').forEach((th) => {
+            if (th !== activeTh) th.removeAttribute('data-sort-dir');
+        });
+
+        if (dataSortDirValue) {
+            activeTh.setAttribute('data-sort-dir', dataSortDirValue);
+        } else {
+            activeTh.removeAttribute('data-sort-dir');
+        }
+
+        table.querySelectorAll('th.sortable, th[data-sort-key]').forEach((th) => {
+            th.removeAttribute('aria-sort');
+        });
+        activeTh.setAttribute('aria-sort', ariaSortValue);
+    }
+
+    function initSortableTable(table) {
+        const headers = Array.from(
+            table.querySelectorAll('th.sortable[data-sort-key], th[data-sort-key]')
+        );
+        const state = { key: null, dir: 1 };
+
+        function activateSort(th) {
+            const key = th.getAttribute('data-sort-key');
+            if (!key) return;
+
+            state.dir = state.key === key ? state.dir * -1 : 1;
+            state.key = key;
+
+            const ariaSort = state.dir === 1 ? 'ascending' : 'descending';
+            const dataSortDir = state.dir === 1 ? 'asc' : 'desc';
+
+            setSortIndicator(table, th, ariaSort, dataSortDir);
+            sortTableBy(table, key, state.dir);
+        }
+
+        headers.forEach((th) => {
+            th.style.cursor = 'pointer';
+
+            th.addEventListener('click', () => activateSort(th));
+
+            // Keyboard support: Enter / Space should sort like a click.
+            th.addEventListener('keydown', (e) => {
+                const k = e.key;
+                if (k === 'Enter' || k === ' ' || k === 'Spacebar') {
+                    e.preventDefault();
+                    activateSort(th);
+                }
+            });
+        });
+
+        // Initial load: show ascending indicator on Name (and sort to match).
+        const defaultKey = 'name';
+        const defaultTh = headers.find((th) => (th.getAttribute('data-sort-key') || '') === defaultKey);
+        if (defaultTh) {
+            state.key = defaultKey;
+            state.dir = 1;
+            setSortIndicator(table, defaultTh, 'ascending', 'asc');
+        }
+    }
+
+    document.addEventListener('DOMContentLoaded', () => {
+        document.querySelectorAll('table[data-sortable]').forEach(initSortableTable);
+    });
+})();
\ No newline at end of file
diff --git a/grails-profiles/web/skeleton/grails-app/assets/stylesheets/welcome.css b/grails-profiles/web/skeleton/grails-app/assets/stylesheets/welcome.css
new file mode 100644
index 0000000000..ba0a393921
--- /dev/null
+++ b/grails-profiles/web/skeleton/grails-app/assets/stylesheets/welcome.css
@@ -0,0 +1,63 @@
+.reload-indicator {
+    display: inline-flex;
+    align-items: center;
+    gap: .5rem;
+    padding: .35rem .6rem;
+    border: 1px solid var(--bs-border-color);
+    border-radius: 999px;
+    background: var(--bs-body-bg);
+    font-size: .875rem;
+    line-height: 1;
+    white-space: nowrap;
+}
+
+.reload-dot {
+    position: relative;
+    width: .6rem;
+    height: .6rem;
+    border-radius: 999px;
+    background: currentColor;
+    flex: 0 0 auto;
+}
+
+.reload-dot.ping::before {
+    content: "";
+    position: absolute;
+    inset: 0;
+    border-radius: 999px;
+    background: currentColor;
+    opacity: .35;
+    animation: reload-ping 1.25s ease-out infinite;
+}
+
+.sortable {
+    cursor: pointer;
+    user-select: none;
+}
+
+.sort-hint {
+    display: inline-block;
+    width: .9em;
+}
+
+.sort-hint::before {
+    content: '';
+}
+
+th.sortable[data-sort-dir="asc"] .sort-hint::before {
+    content: '↑';
+}
+
+th.sortable[data-sort-dir="desc"] .sort-hint::before {
+    content: '↓';
+}
+
+@keyframes reload-ping {
+    0% { transform: scale(1); opacity: .35; }
+    70% { transform: scale(2.25); opacity: 0; }
+    100% { transform: scale(2.25); opacity: 0; }
+}
+
+@media (prefers-reduced-motion: reduce) {
+    .reload-dot::before { animation: none; }
+}
diff --git a/grails-profiles/web/skeleton/grails-app/views/index.gsp b/grails-profiles/web/skeleton/grails-app/views/index.gsp
index c7544050a7..3581e0e031 100644
--- a/grails-profiles/web/skeleton/grails-app/views/index.gsp
+++ b/grails-profiles/web/skeleton/grails-app/views/index.gsp
@@ -1,84 +1,343 @@
-<%@ page import="grails.util.Environment; org.springframework.core.SpringVersion; org.springframework.boot.SpringBootVersion"
-%><!doctype html>
+<%@ page import="grails.util.Environment"%>
+<%@ page import="org.springframework.boot.SpringBootVersion"%>
+<%@ page import="org.springframework.core.SpringVersion"%>
+<g:set var="pluginManager" bean="pluginManager"/>
+<g:set var="servletContext" bean="servletContext"/>
+<g:set var="pluginsWithOrder"
+       value="${pluginManager.allPlugins.toList()
+               .withIndex()
+               .collect { p, i -> [plugin: p, order: i + 1] }
+               .sort { a, b -> a.plugin.name.toLowerCase() <=> b.plugin.name.toLowerCase() }}"
+/>
+<g:set var="numControllers" value="${grailsApplication.controllerClasses.size()}"/>
+<!doctype html>
 <html>
 <head>
-    <meta name="layout" content="main"/>
     <title>Welcome to Grails</title>
+    <meta name="layout" content="main"/>
+    <asset:stylesheet src="welcome.css"/>
 </head>
 <body>
-<content tag="nav">
-    <li class="nav-item dropdown">
-        <a href="#" class="nav-link dropdown-toggle" role="button" data-bs-toggle="dropdown" aria-expanded="false">Application Status <span class="caret"></span></a>
-        <ul class="dropdown-menu">
-            <li><a class="dropdown-item" href="#">Server: ${request.getServletContext().getServerInfo()}</a></li>
-            <li><a class="dropdown-item" href="#">Host: ${InetAddress.getLocalHost()}</a></li>
-            <li><a class="dropdown-item" href="#">Environment: ${Environment.current.name}</a></li>
-            <li><a class="dropdown-item" href="#">App version:
-                <g:meta name="info.app.version"/></a>
-            </li>
-            <li><a class="dropdown-item" href="#">App profile: ${grailsApplication.config.getProperty('grails.profile')}</a></li>
-            <li><hr class="dropdown-divider"></li>
-            <li><a class="dropdown-item" href="#">Grails version:
-                <g:meta name="info.app.grailsVersion"/></a>
-            </li>
-            <li><a class="dropdown-item" href="#">Groovy version: ${GroovySystem.getVersion()}</a></li>
-            <li><a class="dropdown-item" href="#">JVM version: ${System.getProperty('java.version')}</a></li>
-            <li><a class="dropdown-item" href="#">Spring Boot version: ${SpringBootVersion.getVersion()}</a></li>
-            <li><a class="dropdown-item" href="#">Spring version: ${SpringVersion.getVersion()}</a></li>
-            <li><hr class="dropdown-divider"></li>
-            <li><a class="dropdown-item" href="#">Reloading active: ${Environment.reloadingAgentEnabled}</a></li>
-        </ul>
-    </li>
-    <li class="nav-item dropdown">
-        <a href="#" class="nav-link dropdown-toggle" role="button" data-bs-toggle="dropdown" aria-expanded="false">Artefacts <span class="caret"></span></a>
-        <ul class="dropdown-menu">
-            <li><a class="dropdown-item" href="#">Controllers: ${grailsApplication.controllerClasses.size()}</a></li>
-            <li><a class="dropdown-item" href="#">Domains: ${grailsApplication.domainClasses.size()}</a></li>
-            <li><a class="dropdown-item" href="#">Services: ${grailsApplication.serviceClasses.size()}</a></li>
-            <li><a class="dropdown-item" href="#">Tag Libraries: ${grailsApplication.tagLibClasses.size()}</a></li>
-        </ul>
-    </li>
-    <li class="nav-item dropdown">
-        <a href="#" class="nav-link dropdown-toggle" role="button" data-bs-toggle="dropdown" aria-expanded="false">Installed Plugins<span class="caret"></span></a>
-        <ul class="dropdown-menu dropdown-menu-right">
-            <g:each var="plugin" in="${applicationContext.getBean('pluginManager').allPlugins}">
-                <li><a class="dropdown-item" href="#">${plugin.name} - ${plugin.version}</a></li>
-            </g:each>
-        </ul>
-    </li>
-</content>
+<main id="content" role="main" class="pb-4 pb-md-5">
+    <div class="container-lg py-2 py-md-3">
+        <div class="row align-items-top g-4">
 
-<div class="svg" role="presentation">
-    <div class="bg-dark-subtle text-center">
-        <asset:image src="grails-cupsonly-logo-white.svg" class="w-50"/>
+            <%-- WELCOME MESSAGE --%>
+            <div class="col-12 col-md-7">
+                <h1 class="display-6 fw-semibold mb-2">Welcome to Grails</h1>
+                <p class="lead text-body-secondary">
+                    Congratulations, you have successfully started a Grails application.
+                </p>
+                <p class="text-body-secondary">
+                    At the moment this is the default page, feel free to modify it to either
+                    redirect to a controller or display whatever content you may choose.
+                </p>
+            </div>
+
+            <%-- RUNTIME VERSIONS --%>
+            <div class="col-12 col-md-5">
+                <div class="card border-1 shadow-sm">
+                    <div class="card-body">
+                        <h6 class="card-title mb-3 fw-semibold">Runtime versions</h6>
+                        <ul class="list-group list-group-flush small">
+                            <li class="list-group-item d-flex justify-content-between align-items-center px-0">
+                                <span class="d-inline-flex align-items-center text-body-secondary">
+                                    <asset:image src="grails.svg" alt="Grails" width="18" height="18" class="me-2"/>
+                                    Grails
+                                </span>
+                                <g:meta name="info.app.grailsVersion"/>
+                            </li>
+                            <li class="list-group-item d-flex justify-content-between align-items-center px-0">
+                                <span class="d-inline-flex align-items-center text-body-secondary">
+                                    <asset:image src="spring-boot.svg" alt="Spring Boot" width="18" height="18" class="me-2"/>
+                                    Spring Boot
+                                </span>
+                                ${SpringBootVersion.getVersion()}
+                            </li>
+                            <li class="list-group-item d-flex justify-content-between align-items-center px-0">
+                                <span class="d-inline-flex align-items-center text-body-secondary">
+                                    <asset:image src="spring.svg" alt="Spring" width="18" height="18" class="me-2"/>
+                                    Spring
+                                </span>
+                                ${SpringVersion.getVersion()}
+                            </li>
+                            <li class="list-group-item d-flex justify-content-between align-items-center px-0">
+                                <span class="d-inline-flex align-items-center text-body-secondary">
+                                    <asset:image src="groovy.svg" alt="Groovy" width="18" height="18" class="me-2"/>
+                                    Groovy
+                                </span>
+                                ${GroovySystem.getVersion()}
+                            </li>
+                            <li class="list-group-item d-flex justify-content-between align-items-center px-0">
+                                <span class="d-inline-flex align-items-center text-body-secondary">
+                                    <asset:image src="java.svg" alt="Java" width="18" height="18" class="me-2"/>
+                                    JVM (${System.getProperty('java.vendor')})
+                                </span>
+                                ${System.getProperty('java.version')}
+                            </li>
+                        </ul>
+                    </div>
+                </div>
+            </div>
+        </div>
     </div>
-</div>
+    <div class="container-lg">
+        <div class="row g-4 align-items-stretch">
 
-<div id="content" role="main">
-    <div class="container">
-        <section class="row colset-2-its">
-            <h1>Welcome to Grails</h1>
+            <%-- APPLICATION INFO --%>
+            <div class="col-12 col-lg-4">
+                <div class="card border-1 shadow-sm h-100">
+                    <div class="card-body">
+                        <div class="d-flex align-items-center justify-content-between mb-3">
+                            <h6 class="card-title mb-0 fw-semibold">Application</h6>
+                            <g:if test="${Environment.reloadingAgentEnabled}">
+                                <span class="reload-indicator text-success" role="status" aria-label="Reloading active">
+                                    <span class="reload-dot ping" aria-hidden="true"></span>
+                                    <span class="text-body-secondary">Reloading active</span>
+                                </span>
+                            </g:if>
+                            <g:else>
+                                <span class="reload-indicator text-danger" role="status" aria-label="Reloading inactive">
+                                    <span class="reload-dot" aria-hidden="true"></span>
+                                    <span class="text-body-secondary">Reloading inactive</span>
+                                </span>
+                            </g:else>
+                        </div>
+                        <ul class="list-group list-group-flush small">
+                            <li class="list-group-item d-flex justify-content-between align-items-center px-0">
+                                <span class="text-body-secondary">Name</span>
+                                <span class="fw-medium text-truncate ms-3"><g:meta name="info.app.name"/></span>
+                            </li>
+                            <li class="list-group-item d-flex justify-content-between align-items-center px-0">
+                                <span class="text-body-secondary">Version</span>
+                                <span class="fw-medium" style="font-variant-numeric: tabular-nums;">
+                                    <g:meta name="info.app.version"/>
+                                </span>
+                            </li>
+                            <li class="list-group-item d-flex justify-content-between align-items-center px-0">
+                                <span class="text-body-secondary">Profile</span>
+                                <span class="fw-medium text-truncate ms-3">
+                                    ${grailsApplication.config.getProperty('grails.profile')}
+                                </span>
+                            </li>
+                            <li class="list-group-item d-flex justify-content-between align-items-center px-0">
+                                <span class="text-body-secondary">Environment</span>
+                                <span class="fw-medium">${Environment.current.name}</span>
+                            </li>
+                        </ul>
+                    </div>
+                </div>
+            </div>
+
+            <%-- SERVER INFO --%>
+            <div class="col-12 col-lg-4">
+                <div class="card border-1 shadow-sm h-100">
+                    <div class="card-body">
+                        <div class="d-flex align-items-center justify-content-between mb-3">
+                            <h6 class="card-title mb-0 fw-semibold">Server</h6>
+                        </div>
+                        <ul class="list-group list-group-flush small">
+                            <li class="list-group-item d-flex justify-content-between align-items-center px-0">
+                                <span class="text-body-secondary">Servlet Container</span>
+                                <span class="fw-medium text-truncate ms-3">${servletContext.serverInfo}</span>
+                            </li>
+                            <li class="list-group-item d-flex justify-content-between align-items-center px-0">
+                                <span class="text-body-secondary">Host</span>
+                                <span class="fw-medium text-truncate ms-3">${InetAddress.localHost}</span>
+                            </li>
+                            <li class="list-group-item d-flex justify-content-between align-items-center px-0">
+                                <span class="text-body-secondary">OS</span>
+                                <span class="fw-medium text-truncate ms-3">
+                                    ${System.getProperty('os.name')} ${System.getProperty('os.version')} (${System.getProperty('os.arch')})
+                                </span>
+                            </li>
+                        </ul>
+                    </div>
+                </div>
+            </div>
 
-            <p>
-                Congratulations, you have successfully started your first Grails application! At the moment
-                this is the default page, feel free to modify it to either redirect to a controller or display
-                whatever content you may choose. Below is a list of controllers that are currently deployed in
-                this application, click on each to execute its default action:
-            </p>
+            <%-- ARTEFACT COUNTS --%>
+            <div class="col-12 col-lg-4">
+                <div class="card border-1 shadow-sm h-100">
+                    <div class="card-body">
+                        <div class="d-flex align-items-center justify-content-between mb-3">
+                            <h6 class="card-title mb-0 fw-semibold">Artefact counts</h6>
+                        </div>
 
-            <div id="controllers" role="navigation">
-                <h2>Available Controllers:</h2>
-                <ul>
-                    <g:each var="c" in="${grailsApplication.controllerClasses.sort { it.fullName } }">
-                        <li class="controller">
-                            <g:link controller="${c.logicalPropertyName}">${c.fullName}</g:link>
-                        </li>
-                    </g:each>
-                </ul>
+                        <ul class="list-group list-group-flush small">
+                            <li class="list-group-item d-flex justify-content-between align-items-center px-0">
+                                <span class="text-body-secondary">Controllers</span>
+                                <span class="fw-medium">${numControllers}</span>
+                            </li>
+                            <li class="list-group-item d-flex justify-content-between align-items-center px-0">
+                                <span class="text-body-secondary">Domains</span>
+                                <span class="fw-medium">${grailsApplication.domainClasses.size()}</span>
+                            </li>
+                            <li class="list-group-item d-flex justify-content-between align-items-center px-0">
+                                <span class="text-body-secondary">Services</span>
+                                <span class="fw-medium">${grailsApplication.serviceClasses.size()}</span>
+                            </li>
+                            <li class="list-group-item d-flex justify-content-between align-items-center px-0">
+                                <span class="text-body-secondary">Tag Libraries</span>
+                                <span class="fw-medium">${grailsApplication.tagLibClasses.size()}</span>
+                            </li>
+                        </ul>
+                    </div>
+                </div>
             </div>
-        </section>
+
+        </div>
     </div>
-</div>
 
+    <%-- AVAILABLE CONTROLLERS --%>
+    <div class="container-lg mt-4">
+        <div class="row g-4 align-items-start">
+            <div class="col-12 col-lg-7">
+                <div class="card border-1 shadow-sm h-100">
+                    <div class="card-body p-4 p-md-5">
+                        <div class="d-flex flex-column flex-md-row align-items-md-center justify-content-between gap-2">
+                            <div>
+                                <h2 class="h4 mb-1">Available Controllers</h2>
+                                <p class="text-body-secondary mb-0">
+                                    ${numControllers} controller${numControllers != 1 ? 's' : ''} detected.
+                                </p>
+                            </div>
+                            <g:if test="${numControllers != 0}">
+                                <div class="small text-body-secondary">
+                                    Click a controller to execute its default action.
+                                </div>
+                            </g:if>
+                        </div>
+                        <hr class="my-3 my-md-4 text-body-tertiary"/>
+                        <g:set var="controllersByNamespace"
+                               value="${grailsApplication.controllerClasses
+                                       .groupBy { cc -> ((cc.namespace ?: '').trim()) ?: 'default' }
+                                       .sort { a, b -> a.key.toString().toLowerCase() <=> b.key.toString().toLowerCase() }}"/>
+
+                        <g:each var="nsEntry" in="${controllersByNamespace}" status="nsIndex">
+                            <div class="${nsIndex > 0 ? 'mt-4' : ''}">
+                                <div class="px-0 py-2 bg-body-tertiary">
+                                    <div class="d-flex align-items-center justify-content-between">
+                                        <div class="small text-uppercase text-body-secondary fw-semibold"
+                                             style="letter-spacing: .04em;">
+                                            <g:if test="${nsEntry.key != 'default'}">
+                                                ${nsEntry.key}
+                                            </g:if>
+                                            <g:else>
+                                                Default namespace
+                                            </g:else>
+                                        </div>
+                                    </div>
+                                </div>
+
+                                <ul class="list-group list-group-flush">
+                                    <g:each var="c" in="${nsEntry.value.sort { it.fullName }}">
+                                        <g:set var="simpleName" value="${(c.fullName ?: '')
+                                                .tokenize('.')
+                                                .last()
+                                                .replaceFirst(/Controller$/, '')}"/>
+
+                                        <g:set var="controllerUrl"
+                                               value="${createLink(controller: c.logicalPropertyName, namespace: c.namespace)}"/>
+
+                                        <li class="list-group-item px-0">
+                                            <div class="d-flex align-items-center justify-content-between gap-3">
+                                                <g:link controller="${c.logicalPropertyName}"
+                                                        namespace="${c.namespace}"
+                                                        class="d-flex align-items-center gap-3 text-decoration-none min-w-0 flex-grow-1">
+                                                    <div class="min-w-0">
+                                                        <div class="fw-semibold text-body text-truncate">
+                                                            ${simpleName}
+                                                        </div>
+                                                    </div>
+                                                </g:link>
+
+                                                <a href="${controllerUrl}"
+                                                   class="small link-primary link-offset-2 link-underline-opacity-0 link-underline-opacity-75-hover flex-shrink-0">
+                                                    ${controllerUrl}
+                                                </a>
+                                            </div>
+                                        </li>
+                                    </g:each>
+                                </ul>
+                            </div>
+                        </g:each>
+                    </div>
+                </div>
+            </div>
+
+            <%-- PLUGINS --%>
+            <div class="col-12 col-lg-5">
+                <div class="card border-1 shadow-sm h-100">
+                    <div class="card-body">
+                        <div class="d-flex align-items-center justify-content-between mb-3">
+                            <h6 class="card-title mb-0 fw-semibold">Installed plugins</h6>
+                            <span class="badge text-bg-light border">
+                                ${pluginManager.allPlugins.size()}
+                            </span>
+                        </div>
+
+                        <div class="table-responsive">
+                            <table class="table table-sm table-striped table-hover" data-sortable="true">
+                                <thead class="table-light small">
+                                <tr>
+                                    <th scope="col"
+                                        class="text-body-secondary ps-0 fw-semibold sortable"
+                                        data-sort-key="name"
+                                        role="button"
+                                        tabindex="0"
+                                        aria-label="Sort by name">
+                                        Name <span class="sort-hint" aria-hidden="true"></span>
+                                    </th>
+                                    <th scope="col"
+                                        class="text-body-secondary ps-0 fw-semibold text-end sortable"
+                                        data-sort-key="version"
+                                        role="button"
+                                        tabindex="0"
+                                        aria-label="Sort by version">
+                                        <span class="sort-hint" aria-hidden="true"></span> Version
+                                    </th>
+                                    <th scope="col"
+                                        class="text-body-secondary text-end pe-0 sortable"
+                                        data-sort-key="order"
+                                        role="button"
+                                        tabindex="0"
+                                        aria-label="Sort by load order">
+                                        <span class="sort-hint" aria-hidden="true"></span> Load order
+                                    </th>
+                                </tr>
+                                </thead>
+                                <tbody class="small">
+                                <g:each var="row" in="${pluginsWithOrder}">
+                                    <g:set var="pluginName"
+                                           value="${row.plugin.name
+                                                   .replaceAll(/([A-Z]+)([A-Z][a-z])/, '$1 $2')
+                                                   .replaceAll(/([a-z0-9])([A-Z])/, '$1 $2')
+                                                   .replaceAll(/[_-]+/, ' ')
+                                                   .trim()
+                                                   .capitalize()}"
+                                    />
+                                    <tr data-name="${pluginName}" data-version="${row.plugin.version}" data-order="${row.order}">
+                                        <td class="text-truncate">
+                                            ${pluginName}
+                                        </td>
+                                        <td class="text-end" style="font-variant-numeric: tabular-nums;">
+                                            ${row.plugin.version}
+                                        </td>
+                                        <td class="text-end text-body-secondary" style="font-variant-numeric: tabular-nums;">
+                                            ${row.order}
+                                        </td>
+                                    </tr>
+                                </g:each>
+                                </tbody>
+                            </table>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</main>
+<asset:javascript src="welcome.js"/>
 </body>
 </html>
diff --git a/grails-profiles/web/skeleton/grails-app/views/layouts/main.gsp b/grails-profiles/web/skeleton/grails-app/views/layouts/main.gsp
index 6f09bcc995..023449b955 100644
--- a/grails-profiles/web/skeleton/grails-app/views/layouts/main.gsp
+++ b/grails-profiles/web/skeleton/grails-app/views/layouts/main.gsp
@@ -1,12 +1,9 @@
 <!doctype html>
-<html lang="en" class="no-js">
+<html lang="en">
 <head>
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
-    <title>
-    <g:layoutTitle default="Grails"/>
-    </title>
+    <meta charset="utf-8"/>
     <meta name="viewport" content="width=device-width, initial-scale=1"/>
+    <title><g:layoutTitle default="Grails"/></title>
     <asset:link rel="icon" href="favicon.ico" type="image/x-ico"/>
     <asset:stylesheet src="application.css"/>
     <g:layoutHead/>
@@ -14,68 +11,76 @@
 
 <body>
 
-<nav class="navbar navbar-expand-lg bg-body-tertiary">
-    <div class="container-fluid">
-        <a class="navbar-brand" href="/#"><asset:image class="w-75" src="grails.svg" alt="Grails Logo"/></a>
-        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
-            <span class="navbar-toggler-icon"></span>
-        </button>
-
-        <div class="collapse navbar-collapse" aria-expanded="false" id="navbarContent">
-            <ul class="navbar-nav">
-                <g:pageProperty name="page.nav"/>
-            </ul>
-        </div>
+<nav class="navbar navbar-expand-lg bg-body border-bottom shadow-sm">
+    <div class="container-lg">
+        <a class="navbar-brand d-flex align-items-center" href="${request.contextPath}/">
+            <asset:image class="w-75" src="grails.svg" alt="Grails Logo"/>
+        </a>
     </div>
 </nav>
 
-<g:layoutBody/>
+<div class="bg-body-tertiary">
+    <div class="container-lg py-4">
+        <g:layoutBody/>
+    </div>
+</div>
 
-<div class="footer" role="contentinfo">
-    <div class="container-fluid">
-        <div class="row">
-            <div class="card border-0 col-12 col-md">
-                <div class="card-body">
-                    <h6 class="card-title">
-                        <a class="link-underline link-underline-opacity-0" href="https://guides.grails.org" target="_blank">
-                            <asset:image src="advancedgrails.svg" alt="Grails Guides" class="me-2" width="34" />Grails Guides
-                        </a>
-                    </h6>
-                    <p class="card-text">Building your first Grails app? Looking to add security, or create a Single-Page-App? Check out the <a href="https://guides.grails.org" target="_blank">Grails Guides</a> for step-by-step tutorials.</p>
-                </div>
+<footer class="border-top py-5" role="contentinfo">
+    <div class="container-lg">
+        <div class="row g-4">
+            <div class="col-12 col-md-4">
+                <a class="card h-100 text-decoration-none shadow-sm border-1"
+                   href="https://guides.grails.org" target="_blank" rel="noopener">
+                    <div class="card-body p-4">
+                        <div class="d-flex align-items-center justify-content-between mb-2">
+                            <h6 class="card-title mb-0 fw-semibold">Grails Guides</h6>
+                            <asset:image src="advancedgrails.svg" alt="Grails Guides" width="34" height="34"/>
+                        </div>
+                        <p class="card-text text-body-secondary mb-0">
+                            Building your first Grails app? Looking to add security, or create a Single-Page-App?
+                            Check out the Grails Guides for step-by-step tutorials.
+                        </p>
+                    </div>
+                </a>
             </div>
-            <div class="card border-0 col-12 col-md">
-                <div class="card-body">
-                    <h6 class="card-title">
-                        <a class="link-underline link-underline-opacity-0" href="https://grails.apache.org/docs/" target="_blank">
-                            <asset:image src="documentation.svg" alt="Grails Documentation" class="me-2" width="34" />Documentation
-                        </a>
-                    </h6>
-                    <p class="card-text">Ready to dig in? You can find in-depth documentation for all the features of Grails in the <a href="https://grails.apache.org/docs/" target="_blank">User Guide</a>.</p>
-                </div>
+            <div class="col-12 col-md-4">
+                <a class="card h-100 text-decoration-none shadow-sm border-1"
+                   href="https://grails.apache.org/docs/" target="_blank" rel="noopener">
+                    <div class="card-body p-4">
+                        <div class="d-flex align-items-center justify-content-between mb-2">
+                            <h6 class="card-title mb-0 fw-semibold">Documentation</h6>
+                            <asset:image src="documentation.svg" alt="Grails Documentation" width="34" height="34"/>
+                        </div>
+                        <p class="card-text text-body-secondary mb-0">
+                            Ready to dig in? You can find in-depth documentation for all the features
+                            of Grails in the User Guide.
+                        </p>
+                    </div>
+                </a>
             </div>
-            <div class="card border-0 col-12 col-md">
-                <div class="card-body">
-                    <h6 class="card-title">
-                        <a class="link-underline link-underline-opacity-0" href="https://slack.grails.org" target="_blank">
-                            <asset:image src="slack.svg" alt="Grails Slack" class="me-2" width="34" />Join the Community
-                        </a>
-                    </h6>
-                    <p class="card-text">Get feedback and share your experience with other Grails developers in the community <a href="https://slack.grails.org" target="_blank">Slack channel</a>.</p>
-                </div>
+            <div class="col-12 col-md-4">
+                <a class="card h-100 text-decoration-none shadow-sm border-1"
+                   href="https://slack.grails.org" target="_blank" rel="noopener">
+                    <div class="card-body p-4">
+                        <div class="d-flex align-items-center justify-content-between mb-2">
+                            <h6 class="card-title mb-0 fw-semibold">Join the Community</h6>
+                            <asset:image src="slack.svg" alt="Grails Slack" width="34" height="34"/>
+                        </div>
+                        <p class="card-text text-body-secondary mb-0">
+                            Get feedback and share your experience with other Grails developers
+                            in the community Slack channel.
+                        </p>
+                    </div>
+                </a>
             </div>
         </div>
     </div>
-</div>
-
+</footer>
 <div id="spinner" class="position-absolute top-0 end-0 p-1" style="display:none;">
     <div class="spinner-border spinner-border-sm" role="status">
         <span class="visually-hidden">Loading...</span>
     </div>
 </div>
-
-
 <asset:javascript src="application.js"/>
-
 </body>
 </html>