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 <[email protected]>
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>


Reply via email to