~jan0sch/smederee

Showing details for patch 4984ab36d221ab29528b5a63a83b865840983fc8.
2022-09-03 (Sat), 4:51 PM - Jens Grassel - 4984ab36d221ab29528b5a63a83b865840983fc8

VCS: Restructuring, Refactoring and HTML/CSS

- restructured the overview page and navigation
- show more information if available
- refactor uri generation (use baseUri and LinkTools)
- CSS and HTML additions
Summary of changes
1 files added
  • modules/hub/src/main/scala/de/smederee/html/LinkTools.scala
17 files modified with 414 lines added and 322 lines removed
  • modules/hub/src/main/resources/assets/css/main.css with 13 added and 0 removed lines
  • modules/hub/src/main/scala/de/smederee/hub/HubServer.scala with 7 added and 3 removed lines
  • modules/hub/src/main/scala/de/smederee/hub/LandingPageRoutes.scala with 9 added and 5 removed lines
  • modules/hub/src/main/scala/de/smederee/hub/SignupRoutes.scala with 17 added and 9 removed lines
  • modules/hub/src/main/scala/de/smederee/hub/VcsRepositoryRoutes.scala with 94 added and 64 removed lines
  • modules/hub/src/main/twirl/de/smederee/hub/views/createRepository.scala.html with 48 added and 46 removed lines
  • modules/hub/src/main/twirl/de/smederee/hub/views/index.scala.html with 12 added and 12 removed lines
  • modules/hub/src/main/twirl/de/smederee/hub/views/login.scala.html with 35 added and 33 removed lines
  • modules/hub/src/main/twirl/de/smederee/hub/views/main.scala.html with 6 added and 6 removed lines
  • modules/hub/src/main/twirl/de/smederee/hub/views/navbar.scala.html with 8 added and 8 removed lines
  • modules/hub/src/main/twirl/de/smederee/hub/views/showAllRepositories.scala.html with 7 added and 5 removed lines
  • modules/hub/src/main/twirl/de/smederee/hub/views/showRepositories.scala.html with 7 added and 5 removed lines
  • modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryFiles.scala.html with 54 added and 44 removed lines
  • modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryHistory.scala.html with 28 added and 21 removed lines
  • modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryOverview.scala.html with 22 added and 16 removed lines
  • modules/hub/src/main/twirl/de/smederee/hub/views/signup.scala.html with 42 added and 40 removed lines
  • modules/hub/src/main/twirl/de/smederee/hub/views/welcome.scala.html with 5 added and 5 removed lines
1 files removed
  • modules/hub/src/main/twirl/de/smederee/hub/views/createFullPath.scala.html
diff -rN -u old-smederee/modules/hub/src/main/resources/assets/css/main.css new-smederee/modules/hub/src/main/resources/assets/css/main.css
--- old-smederee/modules/hub/src/main/resources/assets/css/main.css	2025-02-02 12:11:36.327101925 +0000
+++ new-smederee/modules/hub/src/main/resources/assets/css/main.css	2025-02-02 12:11:36.331101932 +0000
@@ -26,6 +26,10 @@
   padding: 1em;
 }
 
+.l-box-left-right {
+  padding: 0em 1em 0em 1em;
+}
+
 .l-box-lrg {
   padding: 2em;
   border-bottom: 1px solid rgba(0,0,0,0.1);
@@ -100,6 +104,15 @@
   color: #AECFE5;
 }
 
+.repo-summary-description {
+  background-color: #eee;
+  padding: 0em 0.5em 0em 0.5em;
+}
+
+.repo-summary-description code {
+  word-wrap: break-word;
+}
+
 pre.latest-changes {
   overflow-x: auto;
   overflow-y: hidden;
diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/html/LinkTools.scala new-smederee/modules/hub/src/main/scala/de/smederee/html/LinkTools.scala
--- old-smederee/modules/hub/src/main/scala/de/smederee/html/LinkTools.scala	1970-01-01 00:00:00.000000000 +0000
+++ new-smederee/modules/hub/src/main/scala/de/smederee/html/LinkTools.scala	2025-02-02 12:11:36.331101932 +0000
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022  Contributors as noted in the AUTHORS.md file
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package de.smederee.html
+
+import cats.syntax.all._
+import de.smederee.hub.config.ExternalLinkConfig
+import org.http4s.Uri
+
+object LinkTools {
+
+  extension (linkConfig: ExternalLinkConfig) {
+
+    /** Take the given URI path and create a full URI using the specified configuration with a possible path
+      * prefix and append the given path to it.
+      *
+      * @param path
+      *   An URI containing a path with possible URL fragment and query parameters which will be used to
+      *   construct the full URI.
+      * @return
+      *   A full URI created from the values of the ExternalLinkConfig (scheme, host, port, possible path
+      *   prefix) and the path data from the given URI.
+      */
+    def createFullUri(path: Uri): Uri = {
+      val completePath = linkConfig.path match {
+        case None             => path.path
+        case Some(pathPrefix) => pathPrefix.path |+| path.path
+      }
+      val baseUri = Uri(
+        scheme = Option(linkConfig.scheme),
+        authority = Option(
+          Uri.Authority(
+            userInfo = None,
+            host = Uri.Host.fromIp4sHost(linkConfig.host),
+            port = linkConfig.port.map(_.value)
+          )
+        ),
+        path = completePath
+      ).withQueryParams(path.params)
+      path.fragment.fold(baseUri)(fragment => baseUri.withFragment(fragment))
+    }
+  }
+
+}
diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/HubServer.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/HubServer.scala
--- old-smederee/modules/hub/src/main/scala/de/smederee/hub/HubServer.scala	2025-02-02 12:11:36.327101925 +0000
+++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/HubServer.scala	2025-02-02 12:11:36.331101932 +0000
@@ -109,9 +109,13 @@
         authenticationRepo,
         signAndValidate
       )
-      signUpRepo      = new DoobieSignupRepository[IO](transactor)
-      signUpRoutes    = new SignupRoutes[IO](configuration.service.signup, signUpRepo)
-      landingPages    = new LandingPageRoutes[IO]()
+      signUpRepo = new DoobieSignupRepository[IO](transactor)
+      signUpRoutes = new SignupRoutes[IO](
+        configuration.service.external,
+        configuration.service.signup,
+        signUpRepo
+      )
+      landingPages    = new LandingPageRoutes[IO](configuration.service.external)
       vcsMetadataRepo = new DoobieVcsMetadataRepository[IO](transactor)
       vcsRepoRoutes = new VcsRepositoryRoutes[IO](
         configuration.service.darcs,
diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/LandingPageRoutes.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/LandingPageRoutes.scala
--- old-smederee/modules/hub/src/main/scala/de/smederee/hub/LandingPageRoutes.scala	2025-02-02 12:11:36.327101925 +0000
+++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/LandingPageRoutes.scala	2025-02-02 12:11:36.331101932 +0000
@@ -19,7 +19,9 @@
 
 import cats.effect._
 import cats.syntax.all._
+import de.smederee.html.LinkTools._
 import de.smederee.hub.RequestHelpers.instances.given
+import de.smederee.hub.config.ExternalLinkConfig
 import org.http4s._
 import org.http4s.dsl.Http4sDsl
 import org.http4s.implicits._
@@ -30,25 +32,27 @@
   * Please note that due to the routing logic of http4s catch-all pages (`-> Root`) should be put last in the
   * list of routes!
   *
+  * @param linkConfig
+  *   The configuration needed to build correct links which are working from the outside.
   * @tparam F
   *   A higher kinded type providing needed functionality, which is usually an IO monad like Async or Sync.
   */
-final class LandingPageRoutes[F[_]: Async] extends Http4sDsl[F] {
-  // The URL path that shall be used in the `action` field of the form.
-  private val signupPath = uri"/signup"
+final class LandingPageRoutes[F[_]: Async](linkConfig: ExternalLinkConfig) extends Http4sDsl[F] {
+  // The URL that shall be used in the `action` field of the form.
+  private val signupUri = linkConfig.createFullUri(uri"/signup")
 
   private val mainSiteForLoggedInUsers: AuthedRoutes[Account, F] = AuthedRoutes.of {
     case ar @ GET -> Root as user =>
       for {
         csrf <- Sync[F].delay(ar.req.getCsrfToken)
-        resp <- Ok(views.html.index()()(signupPath, csrf, "Welcome to the Smederee!".some, user.some))
+        resp <- Ok(views.html.index()()(signupUri, csrf, "Welcome to the Smederee!".some, user.some))
       } yield resp
   }
 
   private val mainSite: HttpRoutes[F] = HttpRoutes.of { case req @ GET -> Root =>
     for {
       csrf <- Sync[F].delay(req.getCsrfToken)
-      resp <- Ok(views.html.index()()(signupPath, csrf, "Welcome to the Smederee!".some))
+      resp <- Ok(views.html.index()()(signupUri, csrf, "Welcome to the Smederee!".some))
     } yield resp
   }
 
diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/SignupRoutes.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/SignupRoutes.scala
--- old-smederee/modules/hub/src/main/scala/de/smederee/hub/SignupRoutes.scala	2025-02-02 12:11:36.327101925 +0000
+++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/SignupRoutes.scala	2025-02-02 12:11:36.331101932 +0000
@@ -22,6 +22,7 @@
 import cats.data._
 import cats.effect._
 import cats.syntax.all._
+import de.smederee.html.LinkTools._
 import de.smederee.hub.RequestHelpers.instances.given_RequestHelpers_Request
 import de.smederee.hub.config._
 import de.smederee.hub.forms.types.FormErrors
@@ -38,18 +39,25 @@
 
 /** The routes for handling the user signup process.
   *
-  * @param config
+  * @param linkConfig
+  *   The configuration needed to build correct links which are working from the outside.
+  * @param signupConfig
   *   The configuration for the signup procedure.
   * @param repo
   *   The database repository providing needed functionality for checking and creating accounts.
   * @tparam F
   *   A higher kinded type providing needed functionality, which is usually an IO monad like Async or Sync.
   */
-final class SignupRoutes[F[_]: Async](config: SignupConfiguration, repo: SignupRepository[F])
-    extends Http4sDsl[F] {
+final class SignupRoutes[F[_]: Async](
+    linkConfig: ExternalLinkConfig,
+    signupConfig: SignupConfiguration,
+    repo: SignupRepository[F]
+) extends Http4sDsl[F] {
   private val log = LoggerFactory.getLogger(getClass)
+  // The base URI for our site which that be passed into some templates which create links themselfes.
+  private val baseUri = linkConfig.createFullUri(Uri())
   // The URL path that shall be used in the `action` field of the form.
-  private val signupPath = uri"/signup"
+  private val signupUri = linkConfig.createFullUri(uri"/signup")
 
   // Parse the signup form and take appropriate actions like returning errors or creating an account.
   private val parseSignUpForm = HttpRoutes.of[F] { case request @ POST -> Root / "signup" =>
@@ -89,7 +97,7 @@
           case Validated.Invalid(es) =>
             BadRequest.apply(
               views.html
-                .signup()(signupPath, csrf, "Smederee - Sign up for an account".some)(
+                .signup()(signupUri, csrf, "Smederee - Sign up for an account".some)(
                   formData,
                   FormErrors.fromNec(es)
                 )
@@ -99,7 +107,7 @@
               case Validated.Invalid(es) =>
                 BadRequest.apply(
                   views.html
-                    .signup()(signupPath, csrf, "Smederee - Sign up for an account".some)(
+                    .signup()(signupUri, csrf, "Smederee - Sign up for an account".some)(
                       formData,
                       FormErrors.fromNec(es)
                     )
@@ -118,7 +126,7 @@
                   hash     <- Sync[F].delay(signupForm.password.encode)
                   _        <- Sync[F].delay(log.info(s"Going to create account for ${account.name}."))
                   _        <- repo.createAccount(account, PasswordHash(hash))
-                  redirect <- SeeOther.apply(Location(signupPath.addPath("welcome")))
+                  redirect <- SeeOther.apply(Location(signupUri.addPath("welcome")))
                 } yield redirect
             }
         }
@@ -135,7 +143,7 @@
   private val showSignUpForm = HttpRoutes.of[F] { case request @ GET -> Root / "signup" =>
     for {
       csrf <- Sync[F].delay(request.getCsrfToken)
-      resp <- Ok(views.html.signup()(signupPath, csrf, "Smederee - Sign up for an account".some)())
+      resp <- Ok(views.html.signup()(signupUri, csrf, "Smederee - Sign up for an account".some)())
     } yield resp
   }
 
@@ -148,7 +156,7 @@
     case req @ GET -> Root / "signup" / "welcome" =>
       for {
         csrf <- Sync[F].delay(req.getCsrfToken)
-        resp <- Ok(views.html.welcome()(csrf, "Thank you and welcome!".some))
+        resp <- Ok(views.html.welcome(baseUri)(csrf, "Thank you and welcome!".some))
       } yield resp
   }
 
diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/VcsRepositoryRoutes.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/VcsRepositoryRoutes.scala
--- old-smederee/modules/hub/src/main/scala/de/smederee/hub/VcsRepositoryRoutes.scala	2025-02-02 12:11:36.327101925 +0000
+++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/VcsRepositoryRoutes.scala	2025-02-02 12:11:36.331101932 +0000
@@ -28,6 +28,7 @@
 import de.smederee.hub.RequestHelpers.instances.given
 import de.smederee.hub.config._
 import de.smederee.hub.forms.types.FormErrors
+import de.smederee.html.LinkTools._
 import org.commonmark.parser.Parser
 import org.commonmark.renderer.html.HtmlRenderer
 import org.http4s._
@@ -63,6 +64,9 @@
   private val createRepoPath  = uri"/repo/create"
   private val MaximumFileSize = 131072L // TODO Move to configuration directive.
 
+  // The base URI for our site which that be passed into some templates which create links themselfes.
+  private val baseUri = linkConfig.createFullUri(Uri())
+
   /** Logic for rendering a list of all repositories visible to the given user account.
     *
     * @param csrf
@@ -78,12 +82,55 @@
         .listAllRepositories(user)(VcsMetadataRepositoriesOrdering.NameAscending)
         .compile
         .toList
-      actionBaseUri <- Sync[F].delay(uri"projects")
+      actionBaseUri <- Sync[F].delay(linkConfig.createFullUri(uri"projects"))
       resp <- Ok.apply(
         views.html.showAllRepositories()(actionBaseUri, csrf, s"Smederee - Projects".some, user)(repos)
       )
     } yield resp
 
+  /** Logic for rendering a list of repositories of the given owner for a specific user account. This function
+    * takes visibility into account.
+    *
+    * @param csrf
+    *   An optional CSRF-Token that shall be used.
+    * @param repositoriesOwnerName
+    *   The name of a user whos repositories shall be listed.
+    * @param user
+    *   An optional user account for whom the list of repositories shall be rendered.
+    * @return
+    *   An HTTP response containing the rendered HTML or an error.
+    */
+  private def doShowRepositories(
+      csrf: Option[CsrfToken]
+  )(repositoriesOwnerName: Username)(user: Option[Account]): F[Response[F]] =
+    for {
+      owner <- vcsMetadataRepo.findVcsRepositoryOwner(repositoriesOwnerName)
+      repos <- owner.traverse(owner => vcsMetadataRepo.listRepositories(user)(owner).compile.toList)
+      actionBaseUri <- Sync[F].delay(
+        linkConfig.createFullUri(Uri(path = Uri.Path.unsafeFromString(s"~$repositoriesOwnerName")))
+      )
+      resp <- owner match {
+        case None => // TODO Better error message...
+          NotFound.apply(
+            views.html.showRepositories()(
+              actionBaseUri,
+              csrf,
+              s"Smederee/~$repositoriesOwnerName".some,
+              user
+            )(repos.getOrElse(List.empty), repositoriesOwnerName)
+          )
+        case Some(_) =>
+          Ok.apply(
+            views.html.showRepositories()(
+              actionBaseUri,
+              csrf,
+              s"Smederee/~$repositoriesOwnerName".some,
+              user
+            )(repos.getOrElse(List.empty), repositoriesOwnerName)
+          )
+      }
+    } yield resp
+
   /** Logic for rendering the content of a repository directory or file visible to the given user account.
     *
     * @param csrf
@@ -113,17 +160,11 @@
           loadedRepo.filter(r => r.isPrivate === false || r.owner === user.toVcsRepositoryOwner)
       }
       actionBaseUri <- Sync[F].delay(
-        Uri(
-          scheme = linkConfig.scheme.some,
-          authority = Uri
-            .Authority(
-              userInfo = None,
-              host = Uri.Host.fromIp4sHost(linkConfig.host),
-              port = linkConfig.port.map(_.value)
+        linkConfig.createFullUri(
+          Uri(path =
+            Uri.Path(
+              Vector(Uri.Path.Segment(s"~$repositoryOwnerName"), Uri.Path.Segment(repositoryName.toString))
             )
-            .some,
-          path = Uri.Path.Root |+| Uri.Path(
-            Vector(Uri.Path.Segment(s"~$repositoryOwnerName"), Uri.Path.Segment(repositoryName.toString))
           )
         )
       )
@@ -158,13 +199,13 @@
         case None => NotFound("Repository not found!")
         case Some(repo) =>
           Ok(
-            views.html.showRepositoryOverview()(
+            views.html.showRepositoryOverview(baseUri)(
               actionBaseUri,
               csrf,
               s"Smederee/~$repositoryOwnerName/$repositoryName".some,
               user
             )(
-              repo.some,
+              repo,
               vcsRepositoryHistory = log.stdout.toList.mkString("\n").some,
               vcsRepositoryReadme = readme,
               vcsRepositoryReadmeFilename = readmeName
@@ -248,15 +289,21 @@
           Option(content.mkString("\n"))
       }
       repositoryBaseUri <- Sync[F].delay(
-        Uri(path =
-          Uri.Path.Root |+| Uri.Path(
-            Vector(Uri.Path.Segment(s"~$repositoryOwnerName"), Uri.Path.Segment(repositoryName.toString))
+        linkConfig.createFullUri(
+          Uri(path =
+            Uri.Path(
+              Vector(Uri.Path.Segment(s"~$repositoryOwnerName"), Uri.Path.Segment(repositoryName.toString))
+            )
           )
         )
       )
-      actionBaseUri <- Sync[F].delay(Uri(path = repositoryBaseUri.path.addSegment("files") |+| filePath))
+      actionBaseUri <- Sync[F].delay(
+        linkConfig.createFullUri(Uri(path = repositoryBaseUri.path.addSegment("files") |+| filePath))
+      )
       goBackUri <- Sync[F].delay(
-        Uri(path = Uri.Path.Root |+| Uri.Path(actionBaseUri.path.segments.reverse.drop(1).reverse))
+        linkConfig.createFullUri(
+          Uri(path = Uri.Path(actionBaseUri.path.segments.reverse.drop(1).reverse))
+        )
       )
       resp <-
         repo match {
@@ -269,7 +316,7 @@
               NotFound("File not found!")
             else
               Ok(
-                views.html.showRepositoryFiles()(
+                views.html.showRepositoryFiles(baseUri)(
                   actionBaseUri,
                   csrf,
                   Option(goBackUri),
@@ -332,29 +379,32 @@
       next = if (to < numberOfChanges) Option(to + 1) else None
       history <- darcs.log(directory.toNIO)(repositoryName.toString)(Chain(s"--index=$from-$to", "--summary"))
       actionBaseUri <- Sync[F].delay(
-        Uri(path =
-          Uri.Path.Root |+| Uri.Path(
-            Vector(
-              Uri.Path.Segment(s"~$repositoryOwnerName"),
-              Uri.Path.Segment(repositoryName.toString)
+        linkConfig.createFullUri(
+          Uri(path =
+            Uri.Path(
+              Vector(Uri.Path.Segment(s"~$repositoryOwnerName"), Uri.Path.Segment(repositoryName.toString))
             )
           )
         )
       )
       goBackUri <- Sync[F].delay(
-        Uri(path =
-          Uri.Path.Root |+| Uri.Path(
-            Vector(
-              Uri.Path.Segment(s"~$repositoryOwnerName"),
-              Uri.Path.Segment(repositoryName.toString)
+        linkConfig.createFullUri(
+          Uri(path =
+            Uri.Path(
+              Vector(
+                Uri.Path.Segment(s"~$repositoryOwnerName"),
+                Uri.Path.Segment(repositoryName.toString)
+              )
             )
           )
         )
       )
       repositoryBaseUri <- Sync[F].delay(
-        Uri(path =
-          Uri.Path.Root |+| Uri.Path(
-            Vector(Uri.Path.Segment(s"~$repositoryOwnerName"), Uri.Path.Segment(repositoryName.toString))
+        linkConfig.createFullUri(
+          Uri(path =
+            Uri.Path(
+              Vector(Uri.Path.Segment(s"~$repositoryOwnerName"), Uri.Path.Segment(repositoryName.toString))
+            )
           )
         )
       )
@@ -362,7 +412,7 @@
         case None => NotFound()
         case Some(repo) =>
           Ok.apply(
-            views.html.showRepositoryHistory()(
+            views.html.showRepositoryHistory(baseUri)(
               actionBaseUri,
               csrf,
               Option(goBackUri),
@@ -588,36 +638,16 @@
   private val showRepositories: AuthedRoutes[Account, F] = AuthedRoutes.of {
     case ar @ GET -> Root / UsernamePathParameter(repositoriesOwnerName) as user =>
       for {
-        csrf  <- Sync[F].delay(ar.req.getCsrfToken)
-        owner <- vcsMetadataRepo.findVcsRepositoryOwner(repositoriesOwnerName)
-        repos <- owner.traverse(owner => vcsMetadataRepo.listRepositories(user.some)(owner).compile.toList)
-        actionBaseUri <- Sync[F].delay(
-          Uri(path =
-            Uri.Path.Root |+| Uri.Path(
-              Vector(Uri.Path.Segment(s"~$repositoriesOwnerName"))
-            )
-          )
-        )
-        resp <- owner match {
-          case None => // TODO Better error message...
-            NotFound.apply(
-              views.html.showRepositories()(
-                actionBaseUri,
-                csrf,
-                s"Smederee/~$repositoriesOwnerName".some,
-                user
-              )(repos.getOrElse(List.empty), repositoriesOwnerName)
-            )
-          case Some(_) =>
-            Ok.apply(
-              views.html.showRepositories()(
-                actionBaseUri,
-                csrf,
-                s"Smederee/~$repositoriesOwnerName".some,
-                user
-              )(repos.getOrElse(List.empty), repositoriesOwnerName)
-            )
-        }
+        csrf <- Sync[F].delay(ar.req.getCsrfToken)
+        resp <- doShowRepositories(csrf)(repositoriesOwnerName)(user.some)
+      } yield resp
+  }
+
+  private val showRepositoriesForGuests: HttpRoutes[F] = HttpRoutes.of {
+    case req @ GET -> Root / UsernamePathParameter(repositoriesOwnerName) =>
+      for {
+        csrf <- Sync[F].delay(req.getCsrfToken)
+        resp <- doShowRepositories(csrf)(repositoriesOwnerName)(None)
       } yield resp
   }
 
@@ -685,7 +715,7 @@
     showAllRepositories <+> showRepositories <+> parseCreateRepositoryForm <+> showCreateRepositoryForm <+> showRepositoryOverview <+> showRepositoryHistory <+> showRepositoryFiles
 
   val routes =
-    cloneRepository <+> showAllRepositoriesForGuests <+> showRepositoryOverviewForGuests <+> showRepositoryHistoryForGuests <+> showRepositoryFilesForGuests
+    cloneRepository <+> showAllRepositoriesForGuests <+> showRepositoriesForGuests <+> showRepositoryOverviewForGuests <+> showRepositoryHistoryForGuests <+> showRepositoryFilesForGuests
 
 }
 
diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/createFullPath.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/createFullPath.scala.html
--- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/createFullPath.scala.html	2025-02-02 12:11:36.327101925 +0000
+++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/createFullPath.scala.html	1970-01-01 00:00:00.000000000 +0000
@@ -1,2 +0,0 @@
-@(pathPrefix: Option[Uri] = None)(path: Uri)
-@{path.copy(path = pathPrefix.map(_.path).getOrElse(Uri.Path.empty) |+| path.path)}
diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/createRepository.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/createRepository.scala.html
--- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/createRepository.scala.html	2025-02-02 12:11:36.327101925 +0000
+++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/createRepository.scala.html	2025-02-02 12:11:36.331101932 +0000
@@ -1,55 +1,57 @@
 @import NewVcsRepositoryForm._
 
-@(lang: LanguageCode = LanguageCode("en"), pathPrefix: Option[Uri] = None)(action: Uri, csrf: Option[CsrfToken] = None, title: Option[String] = None, user: Account)(formData: Map[String, String] = Map.empty, formErrors: FormErrors = FormErrors.empty)
-@main(lang, pathPrefix)()(csrf, title, user.some) {
+@(baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en"))(action: Uri, csrf: Option[CsrfToken] = None, title: Option[String] = None, user: Account)(formData: Map[String, String] = Map.empty, formErrors: FormErrors = FormErrors.empty)
+@main(baseUri, lang)()(csrf, title, user.some) {
 @defining(lang.toLocale) { implicit locale =>
   <div class="content">
     <div class="pure-g">
-      <div class="l-box pure-u-1-1 pure-u-md-1-1">
-        <div class="form-errors">
-          @formErrors.get(fieldGlobal).map { es =>
-            @for(error <- es) {
-              <p class="alert alert-error">
-                <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
-                <span class="sr-only">Fehler:</span>
-                @error
-              </p>
+      <div class="pure-u-1-1 pure-u-md-1-1">
+        <div class="l-box">
+          <div class="form-errors">
+            @formErrors.get(fieldGlobal).map { es =>
+              @for(error <- es) {
+                <p class="alert alert-error">
+                  <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
+                  <span class="sr-only">Fehler:</span>
+                  @error
+                </p>
+              }
             }
-          }
-        </div>
-        <div class="create-repo-form">
-          <form action="@createFullPath(pathPrefix)(action)" method="POST" accept-charset="UTF-8" class="pure-form pure-form-aligned">
-            <fieldset id="repository-data">
-              <div class="pure-control-group">
-                <label for="@{fieldName}">@Messages("form.create-repo.name")</label>
-                <input class="pure-input-1-2" id="@{fieldName}" name="@{fieldName}" placeholder="@Messages("form.create-repo.name.placeholder")" maxlength="64" required="" type="text" value="@{formData.get(fieldName)}" autofocus>
-                <small class="pure-form-message" id="@{fieldName}.help">@Messages("form.create-repo.name.help")</small>
-                @renderFormErrors(fieldName, formErrors)
-              </div>
-              <div class="pure-control-group">
-                <label for="@{fieldIsPrivate}">@Messages("form.create-repo.is-private")</label>
-                <input id="@{fieldIsPrivate}" name="@{fieldIsPrivate}" type="checkbox" value="true" @if(formData.get(fieldIsPrivate).map(_ === "true").getOrElse(false)){ checked="" } else { }>
-                <span class="pure-form-message-inline" id="@{fieldIsPrivate}.help">@Messages("form.create-repo.is-private.help")</span>
-                @renderFormErrors(fieldIsPrivate, formErrors)
-              </div>
-              <div class="pure-control-group">
-                <label for="@{fieldDescription}">@Messages("form.create-repo.description")</label>
-                <textarea class="pure-input-1-2" id="@{fieldDescription}" name="@{fieldDescription}" placeholder="@Messages("form.create-repo.description.placeholder")" maxlength="254" rows="3">@{formData.get(fieldDescription)}</textarea>
-                <span class="pure-form-message" id="@{fieldDescription}.help">@Messages("form.create-repo.description.help")</span>
-                @renderFormErrors(fieldDescription, formErrors)
-              </div>
-              <div class="pure-control-group">
-                <label for="@{fieldWebsite}">@Messages("form.create-repo.website")</label>
-                <input id="@{fieldWebsite}" name="@{fieldWebsite}" maxlength="128" placeholder="https://example.com" type="text" value="@{formData.get(fieldWebsite)}">
-                <span class="pure-form-message" id="@{fieldWebsite}.help">@Messages("form.create-repo.website.help")</span>
-                @renderFormErrors(fieldWebsite, formErrors)
-              </div>
-              @csrfToken(csrf)
-              <div class="pure-controls">
-                <button type="submit" class="pure-button">@Messages("form.create-repo.button.submit")</button>
-              </div>
-            </fieldset>
-          </form>
+          </div>
+          <div class="create-repo-form">
+            <form action="@action" method="POST" accept-charset="UTF-8" class="pure-form pure-form-aligned">
+              <fieldset id="repository-data">
+                <div class="pure-control-group">
+                  <label for="@{fieldName}">@Messages("form.create-repo.name")</label>
+                  <input class="pure-input-1-2" id="@{fieldName}" name="@{fieldName}" placeholder="@Messages("form.create-repo.name.placeholder")" maxlength="64" required="" type="text" value="@{formData.get(fieldName)}" autofocus>
+                  <small class="pure-form-message" id="@{fieldName}.help">@Messages("form.create-repo.name.help")</small>
+                  @renderFormErrors(fieldName, formErrors)
+                </div>
+                <div class="pure-control-group">
+                  <label for="@{fieldIsPrivate}">@Messages("form.create-repo.is-private")</label>
+                  <input id="@{fieldIsPrivate}" name="@{fieldIsPrivate}" type="checkbox" value="true" @if(formData.get(fieldIsPrivate).map(_ === "true").getOrElse(false)){ checked="" } else { }>
+                  <span class="pure-form-message-inline" id="@{fieldIsPrivate}.help">@Messages("form.create-repo.is-private.help")</span>
+                  @renderFormErrors(fieldIsPrivate, formErrors)
+                </div>
+                <div class="pure-control-group">
+                  <label for="@{fieldDescription}">@Messages("form.create-repo.description")</label>
+                  <textarea class="pure-input-1-2" id="@{fieldDescription}" name="@{fieldDescription}" placeholder="@Messages("form.create-repo.description.placeholder")" maxlength="254" rows="3">@{formData.get(fieldDescription)}</textarea>
+                  <span class="pure-form-message" id="@{fieldDescription}.help">@Messages("form.create-repo.description.help")</span>
+                  @renderFormErrors(fieldDescription, formErrors)
+                </div>
+                <div class="pure-control-group">
+                  <label for="@{fieldWebsite}">@Messages("form.create-repo.website")</label>
+                  <input id="@{fieldWebsite}" name="@{fieldWebsite}" maxlength="128" placeholder="https://example.com" type="text" value="@{formData.get(fieldWebsite)}">
+                  <span class="pure-form-message" id="@{fieldWebsite}.help">@Messages("form.create-repo.website.help")</span>
+                  @renderFormErrors(fieldWebsite, formErrors)
+                </div>
+                @csrfToken(csrf)
+                <div class="pure-controls">
+                  <button type="submit" class="pure-button">@Messages("form.create-repo.button.submit")</button>
+                </div>
+              </fieldset>
+            </form>
+          </div>
         </div>
       </div>
     </div>
diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/index.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/index.scala.html
--- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/index.scala.html	2025-02-02 12:11:36.327101925 +0000
+++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/index.scala.html	2025-02-02 12:11:36.331101932 +0000
@@ -1,6 +1,6 @@
 @import SignupForm._
 
-@(lang: LanguageCode = LanguageCode("en"), pathPrefix: Option[Uri] = None, tags: MetaTags = MetaTags.empty)(customFooters: Html = Html(""), customHeaders: Html = Html(""))(actionSignup: Uri, csrf: Option[CsrfToken] = None, title: Option[String] = None, user: Option[Account] = None)
+@(baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en"), tags: MetaTags = MetaTags.empty)(customFooters: Html = Html(""), customHeaders: Html = Html(""))(actionSignup: Uri, csrf: Option[CsrfToken] = None, title: Option[String] = None, user: Option[Account] = None)
 @defining(lang.toLocale) { implicit locale =>
 <!DOCTYPE html>
 <html lang="@lang">
@@ -9,16 +9,16 @@
     <meta name="viewport" content="width=device-width, initial-scale=1">
     @meta(tags)
     <title>@title</title>
-    <link rel="stylesheet" href="@pathPrefix/assets/purecss/2.1.0/pure-min.css" />
-    <link rel="stylesheet" href="@pathPrefix/assets/purecss/2.1.0/grids-responsive-min.css" />
-    <link rel="stylesheet" href="@pathPrefix/assets/fontawesome/css/all.min.css" />
-    <link rel="stylesheet" href="@pathPrefix/assets/css/main.css" />
-    <link rel="stylesheet" href="@pathPrefix/assets/css/landingpage.css" />
+    <link rel="stylesheet" href="@{baseUri.addPath("assets/purecss/2.1.0/pure-min.css")}" />
+    <link rel="stylesheet" href="@{baseUri.addPath("assets/purecss/2.1.0/grids-responsive-min.css")}" />
+    <link rel="stylesheet" href="@{baseUri.addPath("assets/fontawesome/css/all.min.css")}" />
+    <link rel="stylesheet" href="@{baseUri.addPath("assets/css/main.css")}" />
+    <link rel="stylesheet" href="@{baseUri.addPath("assets/css/landingpage.css")}" />
     @customHeaders
   </head>
   <body>
     <navbar class="header navbar" id="navbar-top">
-      @navbar(lang, pathPrefix)(csrf, Option("pure-menu-fixed"), user)
+      @navbar(baseUri, lang)(csrf, Option("pure-menu-fixed"), user)
     </navbar>
 
     <div class="splash-container">
@@ -56,7 +56,7 @@
 
       <div class="ribbon l-box-lrg pure-g">
         <div class="l-box-lrg is-center pure-u-1 pure-u-md-1-2 pure-u-lg-2-5">
-          <img width="300" alt="A smithy in which a smith is working on an anvil." class="pure-img-responsive" src="@pathPrefix/assets/img/malcolm-lightbody-gPRvTP0sZ2M-unsplash.jpg">
+          <img width="300" alt="A smithy in which a smith is working on an anvil." class="pure-img-responsive" src="@{baseUri.addPath("assets/img/malcolm-lightbody-gPRvTP0sZ2M-unsplash.jpg")}">
         </div>
         <div class="pure-u-1 pure-u-md-1-2 pure-u-lg-3-5">
           <h2 class="content-head content-head-ribbon">@Messages("landingpage.index.ribbon.title")</h2>
@@ -70,7 +70,7 @@
         <div class="pure-g">
           <div class="l-box-lrg pure-u-1 pure-u-md-2-5">
             <div class="signup-form">
-              <form action="@createFullPath(pathPrefix)(actionSignup)" class="pure-form pure-form-stacked" method="POST" accept-charset="UTF-8" class="pure-form pure-form-aligned" autocomplete="on">
+              <form action="@actionSignup" class="pure-form pure-form-stacked" method="POST" accept-charset="UTF-8" class="pure-form pure-form-aligned" autocomplete="on">
                 <fieldset id="signup-data" @{user.map(_ => "disabled")}>
                   <div class="pure-control-group">
                     <label for="@{fieldName}">@Messages("form.signup.username")</label>
@@ -116,9 +116,9 @@
         <div class="pure-menu pure-menu-horizontal">
           <a class="pure-menu-heading pure-menu-link" href="https://www.wegtam.com" target="_blank">@Messages("global.copyright")</a>
           <ul class="pure-menu-list">
-            <li class="pure-menu-item"><a class="pure-menu-link" href="@pathPrefix/privacy">@Messages("global.privacy")</a>
-            <li class="pure-menu-item"><a class="pure-menu-link" href="@pathPrefix/terms">@Messages("global.terms.of.use")</a>
-            <li class="pure-menu-item"><a class="pure-menu-link" href="@pathPrefix/imprint">@Messages("global.imprint")</a>
+            <li class="pure-menu-item"><a class="pure-menu-link" href="@{baseUri.addPath("privacy")}">@Messages("global.privacy")</a>
+            <li class="pure-menu-item"><a class="pure-menu-link" href="@{baseUri.addPath("terms")}">@Messages("global.terms.of.use")</a>
+            <li class="pure-menu-item"><a class="pure-menu-link" href="@{baseUri.addPath("imprint")}">@Messages("global.imprint")</a>
           </ul>
         </div>
       </footer>
diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/login.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/login.scala.html
--- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/login.scala.html	2025-02-02 12:11:36.327101925 +0000
+++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/login.scala.html	2025-02-02 12:11:36.331101932 +0000
@@ -1,41 +1,43 @@
 @import LoginForm._
 
-@(lang: LanguageCode = LanguageCode("en"), pathPrefix: Option[Uri] = None)(action: Uri, csrf: Option[CsrfToken] = None, title: Option[String] = None)(formData: Map[String, String] = Map.empty, formErrors: FormErrors = FormErrors.empty)
-@main(lang, pathPrefix)()(csrf, title) {
+@(baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en"))(action: Uri, csrf: Option[CsrfToken] = None, title: Option[String] = None)(formData: Map[String, String] = Map.empty, formErrors: FormErrors = FormErrors.empty)
+@main(baseUri, lang)()(csrf, title) {
 @defining(lang.toLocale) { implicit locale =>
 <div class="content">
   <div class="pure-g">
-    <div class="l-box pure-u-1-1 pure-u-md-1-1">
-      <div class="form-errors">
-        @formErrors.get(fieldGlobal).map { es =>
-        @for(error <- es) {
-        <p class="alert alert-error">
-        <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
-        <span class="sr-only">Fehler:</span>
-        @error
-        </p>
-        }
-        }
-      </div>
-      <div class="login-form">
-        <form action="@createFullPath(pathPrefix)(action)" method="POST" accept-charset="UTF-8" class="pure-form pure-form-aligned" autocomplete="on">
-          <fieldset id="login-data">
-            <div class="pure-control-group">
-              <label for="@{fieldName}">@Messages("form.login.username")</label>
-              <input class="pure-input-1-2" id="@{fieldName}" name="@{fieldName}" placeholder="@Messages("form.login.username.placeholder")" maxlength="31" required="" type="text" value="@{formData.get(fieldName)}" autocomplete="username" autofocus>
-              @renderFormErrors(fieldName, formErrors)
-            </div>
-            <div class="pure-control-group">
-              <label for="@{fieldPassword}">@Messages("form.login.password")</label>
-              <input class="pure-input-1-2" id="@{fieldPassword}" name="@{fieldPassword}" placeholder="@Messages("form.login.password.placeholder")" maxlength="128" required="" type="password" value="" autocomplete="password">
-              @renderFormErrors(fieldPassword, formErrors)
-            </div>
-            @csrfToken(csrf)
-            <div class="pure-controls">
-              <button type="submit" class="pure-button">@Messages("form.login.button.submit")</button>
-            </div>
-          </fieldset>
-        </form>
+    <div class="pure-u-1-1 pure-u-md-1-1">
+      <div class="l-box">
+        <div class="form-errors">
+          @formErrors.get(fieldGlobal).map { es =>
+          @for(error <- es) {
+          <p class="alert alert-error">
+          <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
+          <span class="sr-only">Fehler:</span>
+          @error
+          </p>
+          }
+          }
+        </div>
+        <div class="login-form">
+          <form action="@action" method="POST" accept-charset="UTF-8" class="pure-form pure-form-aligned" autocomplete="on">
+            <fieldset id="login-data">
+              <div class="pure-control-group">
+                <label for="@{fieldName}">@Messages("form.login.username")</label>
+                <input class="pure-input-1-2" id="@{fieldName}" name="@{fieldName}" placeholder="@Messages("form.login.username.placeholder")" maxlength="31" required="" type="text" value="@{formData.get(fieldName)}" autocomplete="username" autofocus>
+                @renderFormErrors(fieldName, formErrors)
+              </div>
+              <div class="pure-control-group">
+                <label for="@{fieldPassword}">@Messages("form.login.password")</label>
+                <input class="pure-input-1-2" id="@{fieldPassword}" name="@{fieldPassword}" placeholder="@Messages("form.login.password.placeholder")" maxlength="128" required="" type="password" value="" autocomplete="password">
+                @renderFormErrors(fieldPassword, formErrors)
+              </div>
+              @csrfToken(csrf)
+              <div class="pure-controls">
+                <button type="submit" class="pure-button">@Messages("form.login.button.submit")</button>
+              </div>
+            </fieldset>
+          </form>
+        </div>
       </div>
     </div>
   </div>
diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/main.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/main.scala.html
--- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/main.scala.html	2025-02-02 12:11:36.327101925 +0000
+++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/main.scala.html	2025-02-02 12:11:36.331101932 +0000
@@ -1,4 +1,4 @@
-@(lang: LanguageCode = LanguageCode("en"), pathPrefix: Option[Uri] = None, tags: MetaTags = MetaTags.empty)(customFooters: Html = Html(""), customHeaders: Html = Html(""))(csrf: Option[CsrfToken] = None, title: Option[String] = None, user: Option[Account] = None)(content: Html)
+@(baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en"), tags: MetaTags = MetaTags.empty)(customFooters: Html = Html(""), customHeaders: Html = Html(""))(csrf: Option[CsrfToken] = None, title: Option[String] = None, user: Option[Account] = None)(content: Html)
 @defining(lang.toLocale) { implicit locale =>
 <!DOCTYPE html>
 <html lang="@lang">
@@ -7,14 +7,14 @@
   <meta name="viewport" content="width=device-width, initial-scale=1">
   @meta(tags)
   <title>@title</title>
-  <link rel="stylesheet" href="@pathPrefix/assets/purecss/2.1.0/pure-min.css" />
-  <link rel="stylesheet" href="@pathPrefix/assets/purecss/2.1.0/grids-responsive-min.css" />
-  <link rel="stylesheet" href="@pathPrefix/assets/fontawesome/css/all.min.css" />
-  <link rel="stylesheet" href="@pathPrefix/assets/css/main.css" />
+  <link rel="stylesheet" href="@{baseUri.addPath("assets/purecss/2.1.0/pure-min.css")}" />
+  <link rel="stylesheet" href="@{baseUri.addPath("assets/purecss/2.1.0/grids-responsive-min.css")}" />
+  <link rel="stylesheet" href="@{baseUri.addPath("assets/fontawesome/css/all.min.css")}" />
+  <link rel="stylesheet" href="@{baseUri.addPath("assets/css/main.css")}" />
   @customHeaders
 </head>
 <body>
-  <navbar class="header navbar" id="navbar-top">@navbar(lang, pathPrefix)(csrf, None, user)</navbar>
+  <navbar class="header navbar" id="navbar-top">@navbar(baseUri, lang)(csrf, None, user)</navbar>
   <main class="content-wrapper">
     @content
     <footer class="footer">@customFooters</footer>
diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/navbar.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/navbar.scala.html
--- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/navbar.scala.html	2025-02-02 12:11:36.331101932 +0000
+++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/navbar.scala.html	2025-02-02 12:11:36.331101932 +0000
@@ -1,24 +1,24 @@
-@(lang: LanguageCode, pathPrefix: Option[Uri])(csrf: Option[CsrfToken] = None, extraCss: Option[String] = None, user: Option[Account] = None)
+@(baseUri: Uri, lang: LanguageCode)(csrf: Option[CsrfToken] = None, extraCss: Option[String] = None, user: Option[Account] = None)
 @defining(lang.toLocale) { implicit locale =>
 <nav class="home-menu pure-menu pure-menu-horizontal @extraCss">
-  <a class="logo pure-menu-heading" href="@pathPrefix/">@Messages("global.navbar.top.logo")</a>
+  <a class="logo pure-menu-heading" href="@baseUri">@Messages("global.navbar.top.logo")</a>
 
   <ul class="pure-menu-list">
-    <li class="pure-menu-item"><a class="pure-menu-link" href="@pathPrefix/projects">Projects</a></li>
+    <li class="pure-menu-item"><a class="pure-menu-link" href="@{baseUri.addPath("projects")}">Projects</a></li>
     @if(user.nonEmpty) {
       @for(account <- user) {
-        <li class="pure-menu-item"><a class="pure-menu-link" href="@pathPrefix/~@account.name">Your repositories</a></li>
+        <li class="pure-menu-item"><a class="pure-menu-link" href="@{baseUri.addPath(s"~${account.name}")}">Your projects</a></li>
       }
-      <li class="pure-menu-item"><a class="pure-menu-link" href="@pathPrefix/repo/create">+ @Messages("global.navbar.top.repository.new")</a></li>
+      <li class="pure-menu-item"><a class="pure-menu-link" href="@{baseUri.addPath("repo/create")}">+ @Messages("global.navbar.top.repository.new")</a></li>
       <li class="pure-menu-item">
-        <form action="@pathPrefix/logout" method="POST" accept-charset="UTF-8" class="pure-form">
+        <form action="@{baseUri.addPath("logout")}" method="POST" accept-charset="UTF-8" class="pure-form">
           @csrfToken(csrf)
           <button class="pure-button pure-button-primary" type="submit">@Messages("global.logout")</button>
         </form>
       </li>
     } else {
-      <li class="pure-menu-item"><a class="pure-menu-link" href="@pathPrefix/login">@Messages("global.login")</a></li>
-      <li class="pure-menu-item"><a class="pure-menu-link" href="@pathPrefix/signup">@Messages("global.signup")</a></li>
+      <li class="pure-menu-item"><a class="pure-menu-link" href="@{baseUri.addPath("login")}">@Messages("global.login")</a></li>
+      <li class="pure-menu-item"><a class="pure-menu-link" href="@{baseUri.addPath("signup")}">@Messages("global.signup")</a></li>
     }
   </ul>
 </nav>
diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showAllRepositories.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showAllRepositories.scala.html
--- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showAllRepositories.scala.html	2025-02-02 12:11:36.331101932 +0000
+++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showAllRepositories.scala.html	2025-02-02 12:11:36.331101932 +0000
@@ -1,9 +1,10 @@
-@(lang: LanguageCode = LanguageCode("en"), pathPrefix: Option[Uri] = None)(actionBaseUri: Uri, csrf: Option[CsrfToken] = None, title: Option[String] = None, user: Option[Account])(listing: List[VcsRepository])
-@main(lang, pathPrefix)()(csrf, title, user) {
+@(baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en"))(actionBaseUri: Uri, csrf: Option[CsrfToken] = None, title: Option[String] = None, user: Option[Account])(listing: List[VcsRepository])
+@main(baseUri, lang)()(csrf, title, user) {
 @defining(lang.toLocale) { implicit locale =>
   <div class="content">
     <div class="pure-g">
-      <div class="l-box pure-u-1-1 pure-u-md-1-1">
+      <div class="pure-u-1-1 pure-u-md-1-1">
+        <div class="l-box">
         @if(listing.nonEmpty) {
         <table class="pure-table pure-table-horizontal">
           <thead>
@@ -18,9 +19,9 @@
             <tr>
               <td><i class="fa-solid fa-folder-tree"></i> @if(repo.isPrivate){<i class="fa-solid fa-lock"></i>}else{ }</td>
               <td>
-                <a href="@createFullPath(pathPrefix)(Uri(path = Uri.Path.Root).addSegment(s"~${repo.owner.name.toString}"))">~@{repo.owner.name}</a>
+                <a href="@{baseUri.addSegment(s"~${repo.owner.name.toString}")}">~@{repo.owner.name}</a>
                 /
-                <a href="@createFullPath(pathPrefix)(Uri(path = Uri.Path.Root).addSegment(s"~${repo.owner.name.toString}").addSegment(repo.name.toString))">@{repo.name}</a>
+                <a href="@{baseUri.addSegment(s"~${repo.owner.name.toString}").addSegment(repo.name.toString)}">@{repo.name}</a>
               </td>
               <td>@repo.description</td>
             </tr>
@@ -30,6 +31,7 @@
         } else {
           <div class="alert alert-warning">No repositories found.</div>
         }
+        </div>
       </div>
     </div>
   </div>
diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositories.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositories.scala.html
--- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositories.scala.html	2025-02-02 12:11:36.331101932 +0000
+++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositories.scala.html	2025-02-02 12:11:36.331101932 +0000
@@ -1,9 +1,10 @@
-@(lang: LanguageCode = LanguageCode("en"), pathPrefix: Option[Uri] = None)(actionBaseUri: Uri, csrf: Option[CsrfToken] = None, title: Option[String] = None, user: Account)(listing: List[VcsRepository], repositoriesOwner: Username)
-@main(lang, pathPrefix)()(csrf, title, user.some) {
+@(baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en"))(actionBaseUri: Uri, csrf: Option[CsrfToken] = None, title: Option[String] = None, user: Option[Account])(listing: List[VcsRepository], repositoriesOwner: Username)
+@main(baseUri, lang)()(csrf, title, user) {
 @defining(lang.toLocale) { implicit locale =>
   <div class="content">
     <div class="pure-g">
-      <div class="l-box pure-u-1-1 pure-u-md-1-1">
+      <div class="pure-u-1-1 pure-u-md-1-1">
+        <div class="l-box">
         @if(listing.nonEmpty) {
         <table class="pure-table pure-table-horizontal">
           <thead>
@@ -17,15 +18,16 @@
             @for(repo <- listing) {
             <tr>
               <td><i class="fa-solid fa-folder-tree"></i> @if(repo.isPrivate){<i class="fa-solid fa-lock"></i>}else{ }</td>
-              <td><a href="@createFullPath(pathPrefix)(actionBaseUri.addSegment(repo.name.toString))">@{repo.name}</a></td>
+              <td><a href="@{actionBaseUri.addSegment(repo.name.toString)}">@{repo.name}</a></td>
               <td>@repo.description</td>
             </tr>
             }
           </tbody>
         </table>
         } else {
-          <div class="alert">No repositories found.</div>
+          <div class="alert alert-secondary">No repositories found.</div>
         }
+        </div>
       </div>
     </div>
   </div>
diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryFiles.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryFiles.scala.html
--- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryFiles.scala.html	2025-02-02 12:11:36.331101932 +0000
+++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryFiles.scala.html	2025-02-02 12:11:36.331101932 +0000
@@ -1,59 +1,69 @@
-@(lang: LanguageCode = LanguageCode("en"), pathPrefix: Option[Uri] = None)(actionBaseUri: Uri, csrf: Option[CsrfToken] = None, goBackUri: Option[Uri] = None, title: Option[String] = None, user: Option[Account])(fileContent: Option[String], listing: IndexedSeq[(os.RelPath, os.StatInfo)], repositoryBaseUri: Uri, repository: VcsRepository)
-@main(lang, pathPrefix)()(csrf, title, user) {
+@(baseUri: Uri, lang: LanguageCode = LanguageCode("en"))(actionBaseUri: Uri, csrf: Option[CsrfToken] = None, goBackUri: Option[Uri] = None, title: Option[String] = None, user: Option[Account])(fileContent: Option[String], listing: IndexedSeq[(os.RelPath, os.StatInfo)], repositoryBaseUri: Uri, vcsRepository: VcsRepository)
+@main(baseUri, lang)()(csrf, title, user) {
 @defining(lang.toLocale) { implicit locale =>
   <div class="content">
     <div class="pure-g">
-      <div class="l-box pure-u-1-1 pure-u-md-1-1">
-        <h2>~@{repository.owner.name}/@{repository.name}</h2>
-        <nav class="pure-menu pure-menu-horizontal">
-          <ul class="pure-menu-list">
-            <li class="pure-menu-item"><a class="pure-menu-link" href="@createFullPath(pathPrefix)(repositoryBaseUri)"><i class="fa-solid fa-eye"></i> Overview</a></li>
-            <li class="pure-menu-item pure-menu-active"><a class="pure-menu-link" href="@createFullPath(pathPrefix)(repositoryBaseUri.addSegment("files"))"><i class="fa-solid fa-folder-tree"></i> Files</a></li>
-            <li class="pure-menu-item"><a class="pure-menu-link" href="@createFullPath(pathPrefix)(repositoryBaseUri.addSegment("history"))"><i class="fa-solid fa-timeline"></i> Changes</a></li>
-          </ul>
-        </nav>
+      <div class="pure-u-1-1 pure-u-md-1-1">
+        <div class="l-box-left-right">
+          <h2><a href="@{baseUri.addSegment(s"~${vcsRepository.owner.name}")}">~@vcsRepository.owner.name</a>/@vcsRepository.name</h2>
+          <nav class="pure-menu pure-menu-horizontal">
+            <ul class="pure-menu-list">
+              <li class="pure-menu-item"><a class="pure-menu-link" href="@repositoryBaseUri"><i class="fa-solid fa-eye"></i> Overview</a></li>
+              <li class="pure-menu-item pure-menu-active"><a class="pure-menu-link" href="@repositoryBaseUri.addSegment("files")"><i class="fa-solid fa-folder-tree"></i> Files</a></li>
+              <li class="pure-menu-item"><a class="pure-menu-link" href="@repositoryBaseUri.addSegment("history")"><i class="fa-solid fa-timeline"></i> Changes</a></li>
+              @for(website <- vcsRepository.website) {
+              <li class="pure-menu-item"><a class="pure-menu-link" href="@website" target="_blank" title="Click here to open the project website (@website) in a new tab or window."><i class="fa-solid fa-up-right-from-square"></i> Website</a></li>
+              }
+            </ul>
+          </nav>
+          <div class="repo-summary-description">
+            <code>@{actionBaseUri.path.toString.replaceFirst("/files", "")}</code>
+          </div>
+        </div>
       </div>
     </div>
   </div>
   <div class="content">
     <div class="pure-g">
-      <div class="l-box pure-u-1-1 pure-u-md-1-1">
-        @if(fileContent.isEmpty) {
-        <table class="pure-table pure-table-horizontal">
-          <thead>
-            <tr>
-              <th></th>
-              <th>Name</th>
-              <th>Size</th>
-              <th>Modified</th>
-            </tr>
-          </thead>
-          <tbody>
+      <div class="pure-u-1-1 pure-u-md-1-1">
+        <div class="l-box">
+          @if(fileContent.isEmpty) {
+          <table class="pure-table pure-table-horizontal">
+            <thead>
+              <tr>
+                <th></th>
+                <th>Name</th>
+                <th>Size</th>
+                <th>Modified</th>
+              </tr>
+            </thead>
+            <tbody>
+              @for(link <- goBackUri) {
+              <tr>
+                <td><a href="@link"><i class="fa-solid fa-angle-up"></i></a></td>
+                <td><a href="@link">..</a></td>
+                <td></td>
+              </tr>
+              }
+              @for(entry <- listing) {
+              <tr>
+                <td>@if(entry._2.isDir) { <i class="fa-solid fa-folder"></i> } else { <i class="fa-solid fa-file"></i> }</td>
+                <td><a href="@actionBaseUri.addSegment(entry._1.last)">@{entry._1}</a></td>
+                <td>@{entry._2.size}</td>
+                <td>@{entry._2.mtime}</td>
+              </tr>
+              }
+            </tbody>
+          </table>
+          } else {
             @for(link <- goBackUri) {
-            <tr>
-              <td><a href="@createFullPath(pathPrefix)(link)"><i class="fa-solid fa-angle-up"></i></a></td>
-              <td><a href="@createFullPath(pathPrefix)(link)">..</a></td>
-              <td></td>
-            </tr>
+              <a href="@link"><i class="fa-solid fa-angle-up"></i></a>&nbsp;<a href="@link">..</a>
             }
-            @for(entry <- listing) {
-            <tr>
-              <td>@if(entry._2.isDir) { <i class="fa-solid fa-folder"></i> } else { <i class="fa-solid fa-file"></i> }</td>
-              <td><a href="@createFullPath(pathPrefix)(actionBaseUri.addSegment(entry._1.last))">@{entry._1}</a></td>
-              <td>@{entry._2.size}</td>
-              <td>@{entry._2.mtime}</td>
-            </tr>
+            @for(content <- fileContent) {
+              <pre class="repository-file-content"><code>@content</code></pre>
             }
-          </tbody>
-        </table>
-        } else {
-          @for(link <- goBackUri) {
-            <a href="@createFullPath(pathPrefix)(link)"><i class="fa-solid fa-angle-up"></i></a>&nbsp;<a href="@createFullPath(pathPrefix)(link)">..</a>
           }
-          @for(content <- fileContent) {
-            <pre class="repository-file-content"><code>@content</code></pre>
-          }
-        }
+        </div>
       </div>
     </div>
   </div>
diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryHistory.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryHistory.scala.html
--- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryHistory.scala.html	2025-02-02 12:11:36.331101932 +0000
+++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryHistory.scala.html	2025-02-02 12:11:36.331101932 +0000
@@ -1,33 +1,40 @@
-@(lang: LanguageCode = LanguageCode("en"), pathPrefix: Option[Uri] = None)(actionBaseUri: Uri, csrf: Option[CsrfToken] = None, goBackUri: Option[Uri] = None, title: Option[String] = None, user: Option[Account])(history: String, nextEntry: Option[Int], repositoryBaseUri: Uri, repository: VcsRepository)
-@main(lang, pathPrefix)()(csrf, title, user) {
+@(baseUri: Uri, lang: LanguageCode = LanguageCode("en"))(actionBaseUri: Uri, csrf: Option[CsrfToken] = None, goBackUri: Option[Uri] = None, title: Option[String] = None, user: Option[Account])(history: String, nextEntry: Option[Int], repositoryBaseUri: Uri, vcsRepository: VcsRepository)
+@main(baseUri, lang)()(csrf, title, user) {
 @defining(lang.toLocale) { implicit locale =>
   <div class="content">
     <div class="pure-g">
-      <div class="l-box pure-u-1-1 pure-u-md-1-1">
-        <h2>~@{repository.owner.name}/@{repository.name}</h2>
-        <nav class="pure-menu pure-menu-horizontal">
-          <ul class="pure-menu-list">
-            <li class="pure-menu-item"><a class="pure-menu-link" href="@createFullPath(pathPrefix)(actionBaseUri)"><i class="fa-solid fa-eye"></i> Overview</a></li>
-            <li class="pure-menu-item"><a class="pure-menu-link" href="@createFullPath(pathPrefix)(actionBaseUri.addSegment("files"))"><i class="fa-solid fa-folder-tree"></i> Files</a></li>
-            <li class="pure-menu-item pure-menu-active"><a class="pure-menu-link" href="@createFullPath(pathPrefix)(actionBaseUri.addSegment("history"))"><i class="fa-solid fa-timeline"></i> Changes</a></li>
-          </ul>
-        </nav>
+      <div class="pure-u-1-1 pure-u-md-1-1">
+        <div class="l-box-left-right">
+          <h2><a href="@{baseUri.addSegment(s"~${vcsRepository.owner.name}")}">~@vcsRepository.owner.name</a>/@vcsRepository.name</h2>
+          <nav class="pure-menu pure-menu-horizontal">
+            <ul class="pure-menu-list">
+              <li class="pure-menu-item"><a class="pure-menu-link" href="@actionBaseUri"><i class="fa-solid fa-eye"></i> Overview</a></li>
+              <li class="pure-menu-item"><a class="pure-menu-link" href="@actionBaseUri.addSegment("files")"><i class="fa-solid fa-folder-tree"></i> Files</a></li>
+              <li class="pure-menu-item pure-menu-active"><a class="pure-menu-link" href="@actionBaseUri.addSegment("history")"><i class="fa-solid fa-timeline"></i> Changes</a></li>
+              @for(website <- vcsRepository.website) {
+              <li class="pure-menu-item"><a class="pure-menu-link" href="@website" target="_blank" title="Click here to open the project website (@website) in a new tab or window."><i class="fa-solid fa-up-right-from-square"></i> Website</a></li>
+              }
+            </ul>
+          </nav>
+        </div>
       </div>
     </div>
   </div>
   <div class="content">
     <div class="pure-g">
-      <div class="l-box pure-u-1-1 pure-u-md-1-1">
-        <pre><code>@history</code></pre>
+      <div class="pure-u-1-1 pure-u-md-1-1">
+        <div class="l-box"><pre><code>@history</code></pre></div>
       </div>
-      <div class="l-box pure-u-1-1 pure-u-md-1-1">
-        <nav class="pure-menu pure-menu-horizontal">
-          <ul class="pure-menu-list">
-            @for(next <- nextEntry) {
-            <li class="pure-menu-item"><a class="pure-menu-link" href="@createFullPath(pathPrefix)(repositoryBaseUri.addSegment("history").withOptionQueryParam("from", nextEntry))">Next <i class="fa-solid fa-angle-right"></i></a></li>
-            }
-          </ul>
-        </nav>
+      <div class="pure-u-1-1 pure-u-md-1-1">
+        <div class="l-box">
+          <nav class="pure-menu pure-menu-horizontal">
+            <ul class="pure-menu-list">
+              @for(next <- nextEntry) {
+              <li class="pure-menu-item"><a class="pure-menu-link" href="@repositoryBaseUri.addSegment("history").withOptionQueryParam("from", nextEntry)">Next <i class="fa-solid fa-angle-right"></i></a></li>
+              }
+            </ul>
+          </nav>
+        </div>
       </div>
     </div>
   </div>
diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryOverview.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryOverview.scala.html
--- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryOverview.scala.html	2025-02-02 12:11:36.331101932 +0000
+++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryOverview.scala.html	2025-02-02 12:11:36.331101932 +0000
@@ -1,22 +1,29 @@
-@(lang: LanguageCode = LanguageCode("en"), pathPrefix: Option[Uri] = None)(actionBaseUri: Uri, csrf: Option[CsrfToken] = None, title: Option[String] = None, user: Option[Account])(vcsRepository: Option[VcsRepository], vcsRepositoryHistory: Option[String], vcsRepositoryReadme: Option[String] = None, vcsRepositoryReadmeFilename: Option[String] = None)
-@main(lang, pathPrefix)()(csrf, title, user) {
+@(baseUri: Uri, lang: LanguageCode = LanguageCode("en"))(actionBaseUri: Uri, csrf: Option[CsrfToken] = None, title: Option[String] = None, user: Option[Account])(vcsRepository: VcsRepository, vcsRepositoryHistory: Option[String], vcsRepositoryReadme: Option[String] = None, vcsRepositoryReadmeFilename: Option[String] = None)
+@main(baseUri, lang)()(csrf, title, user) {
 @defining(lang.toLocale) { implicit locale =>
-  @for(repo <- vcsRepository) {
   <div class="content">
     <div class="pure-g">
-      <div class="l-box pure-u-1-1 pure-u-md-1-1">
-        <h2>~@repo.owner.name/@repo.name</h2>
-        <nav class="pure-menu pure-menu-horizontal">
-          <ul class="pure-menu-list">
-            <li class="pure-menu-item pure-menu-active"><a class="pure-menu-link" href="@createFullPath(pathPrefix)(actionBaseUri)"><i class="fa-solid fa-eye"></i> Overview</a></li>
-            <li class="pure-menu-item"><a class="pure-menu-link" href="@createFullPath(pathPrefix)(actionBaseUri.addSegment("files"))"><i class="fa-solid fa-folder-tree"></i> Files</a></li>
-            <li class="pure-menu-item"><a class="pure-menu-link" href="@createFullPath(pathPrefix)(actionBaseUri.addSegment("history"))"><i class="fa-solid fa-timeline"></i> Changes</a></li>
-          </ul>
-        </nav>
+      <div class="pure-u-1-1 pure-u-md-1-1">
+        <div class="l-box-left-right">
+          <h2><a href="@{baseUri.addSegment(s"~${vcsRepository.owner.name}")}">~@vcsRepository.owner.name</a>/@vcsRepository.name</h2>
+          <nav class="pure-menu pure-menu-horizontal">
+            <ul class="pure-menu-list">
+              <li class="pure-menu-item pure-menu-active"><a class="pure-menu-link" href="@actionBaseUri"><i class="fa-solid fa-eye"></i> Overview</a></li>
+              <li class="pure-menu-item"><a class="pure-menu-link" href="@actionBaseUri.addSegment("files")"><i class="fa-solid fa-folder-tree"></i> Files</a></li>
+              <li class="pure-menu-item"><a class="pure-menu-link" href="@actionBaseUri.addSegment("history")"><i class="fa-solid fa-timeline"></i> Changes</a></li>
+              @for(website <- vcsRepository.website) {
+              <li class="pure-menu-item"><a class="pure-menu-link" href="@website" target="_blank" title="Click here to open the project website (@website) in a new tab or window."><i class="fa-solid fa-up-right-from-square"></i> Website</a></li>
+              }
+            </ul>
+          </nav>
+          @for(description <- vcsRepository.description) {
+            <div class="repo-summary-description">
+              <strong>Summary:</strong> @description
+            </div>
+          }
+        </div>
       </div>
     </div>
-  </div>
-  <div class="content">
     <div class="pure-g">
       <div class="pure-u-3-5 pure-u-md-3-5">
         <div class="l-box">
@@ -32,7 +39,7 @@
             <dd>
               <form class="pure-form">
                 <fieldset>
-                  <input class="pure-input-1" type="text" value="@{createFullPath(pathPrefix)(actionBaseUri)}.darcs" readonly="readonly"/>
+                  <input class="pure-input-1" type="text" value="@{actionBaseUri}.darcs" readonly="readonly"/>
                 </fieldset>
               </form>
             </dd>
@@ -58,6 +65,5 @@
       </div>
     </div>
   </div>
-  }
 }
 }
diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/signup.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/signup.scala.html
--- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/signup.scala.html	2025-02-02 12:11:36.331101932 +0000
+++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/signup.scala.html	2025-02-02 12:11:36.331101932 +0000
@@ -1,49 +1,51 @@
 @import SignupForm._
 
-@(lang: LanguageCode = LanguageCode("en"), pathPrefix: Option[Uri] = None)(action: Uri, csrf: Option[CsrfToken] = None, title: Option[String] = None)(formData: Map[String, String] = Map.empty, formErrors: FormErrors = FormErrors.empty)
-@main(lang, pathPrefix)()(csrf, title) {
+@(baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en"))(action: Uri, csrf: Option[CsrfToken] = None, title: Option[String] = None)(formData: Map[String, String] = Map.empty, formErrors: FormErrors = FormErrors.empty)
+@main(baseUri, lang)()(csrf, title) {
 @defining(lang.toLocale) { implicit locale =>
   <div class="content">
     <div class="pure-g">
-      <div class="l-box pure-u-1-1 pure-u-md-1-1">
-        <div class="form-errors">
-          @formErrors.get(fieldGlobal).map { es =>
-            @for(error <- es) {
-              <p class="alert alert-error">
-                <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
-                <span class="sr-only">Fehler:</span>
-                @error
-              </p>
+      <div class="pure-u-1-1 pure-u-md-1-1">
+        <div class="l-box">
+          <div class="form-errors">
+            @formErrors.get(fieldGlobal).map { es =>
+              @for(error <- es) {
+                <p class="alert alert-error">
+                  <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
+                  <span class="sr-only">Fehler:</span>
+                  @error
+                </p>
+              }
             }
-          }
-        </div>
-        <div class="signup-form">
-          <form action="@createFullPath(pathPrefix)(action)" method="POST" accept-charset="UTF-8" class="pure-form pure-form-aligned" autocomplete="on">
-            <fieldset id="signup-data">
-              <div class="pure-control-group">
-                <label for="@{fieldName}">@Messages("form.signup.username")</label>
-                <input class="pure-input-1-2" id="@{fieldName}" name="@{fieldName}" placeholder="@Messages("form.signup.username.placeholder")" maxlength="31" required="" type="text" value="@{formData.get(fieldName)}" autocomplete="username" autofocus>
-                <small class="pure-form-message" id="@{fieldName}.help">@Messages("form.signup.username.help")</small>
-                @renderFormErrors(fieldName, formErrors)
-              </div>
-              <div class="pure-control-group">
-                <label for="@{fieldEmail}">Email address</label>
-                <input class="pure-input-1-2" id="@{fieldEmail}" name="@{fieldEmail}" placeholder="some@@somewhere.org" maxlength="128" required="" type="email" value="@{formData.get(fieldEmail)}" autocomplete="email">
-                <small class="pure-form-message" id="@{fieldEmail}.help">Please enter your email address.</small>
-                @renderFormErrors(fieldEmail, formErrors)
-              </div>
-              <div class="pure-control-group">
-                <label for="@{fieldPassword}">Password</label>
-                <input class="pure-input-1-2" id="@{fieldPassword}" name="@{fieldPassword}" placeholder="Please choose a secure password!" maxlength="128" required="" type="password" value="" autocomplete="new-password">
-                <small class="pure-form-message" id="@{fieldPassword}.help">Your password must be at least 12 characters long.</small>
-                @renderFormErrors(fieldPassword, formErrors)
-              </div>
-              @csrfToken(csrf)
-              <div class="pure-controls">
-                <button type="submit" class="pure-button">@Messages("form.signup.button.submit")</button>
-              </div>
-            </fieldset>
-          </form>
+          </div>
+          <div class="signup-form">
+            <form action="@action" method="POST" accept-charset="UTF-8" class="pure-form pure-form-aligned" autocomplete="on">
+              <fieldset id="signup-data">
+                <div class="pure-control-group">
+                  <label for="@{fieldName}">@Messages("form.signup.username")</label>
+                  <input class="pure-input-1-2" id="@{fieldName}" name="@{fieldName}" placeholder="@Messages("form.signup.username.placeholder")" maxlength="31" required="" type="text" value="@{formData.get(fieldName)}" autocomplete="username" autofocus>
+                  <small class="pure-form-message" id="@{fieldName}.help">@Messages("form.signup.username.help")</small>
+                  @renderFormErrors(fieldName, formErrors)
+                </div>
+                <div class="pure-control-group">
+                  <label for="@{fieldEmail}">Email address</label>
+                  <input class="pure-input-1-2" id="@{fieldEmail}" name="@{fieldEmail}" placeholder="some@@somewhere.org" maxlength="128" required="" type="email" value="@{formData.get(fieldEmail)}" autocomplete="email">
+                  <small class="pure-form-message" id="@{fieldEmail}.help">Please enter your email address.</small>
+                  @renderFormErrors(fieldEmail, formErrors)
+                </div>
+                <div class="pure-control-group">
+                  <label for="@{fieldPassword}">Password</label>
+                  <input class="pure-input-1-2" id="@{fieldPassword}" name="@{fieldPassword}" placeholder="Please choose a secure password!" maxlength="128" required="" type="password" value="" autocomplete="new-password">
+                  <small class="pure-form-message" id="@{fieldPassword}.help">Your password must be at least 12 characters long.</small>
+                  @renderFormErrors(fieldPassword, formErrors)
+                </div>
+                @csrfToken(csrf)
+                <div class="pure-controls">
+                  <button type="submit" class="pure-button">@Messages("form.signup.button.submit")</button>
+                </div>
+              </fieldset>
+            </form>
+          </div>
         </div>
       </div>
     </div>
diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/welcome.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/welcome.scala.html
--- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/welcome.scala.html	2025-02-02 12:11:36.331101932 +0000
+++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/welcome.scala.html	2025-02-02 12:11:36.331101932 +0000
@@ -1,18 +1,18 @@
-@(lang: LanguageCode = LanguageCode("en"), pathPrefix: Option[Uri] = None)(csrf: Option[CsrfToken] = None, title: Option[String] = None)
-@main(lang, pathPrefix)()(csrf, title) {
+@(baseUri: Uri, lang: LanguageCode = LanguageCode("en"))(csrf: Option[CsrfToken] = None, title: Option[String] = None)
+@main(baseUri, lang)()(csrf, title) {
 @defining(lang.toLocale) { implicit locale =>
   <div class="content">
-    <h2 class="content-head is-center" style="color: #34495e; text-align: center;">@Messages("landingpage.welcome.title")</h2>
+    <h1 class="content-head is-center" style="color: #34495e; text-align: center;">@Messages("landingpage.welcome.title")</h2>
   </div>
   <div class="content">
     <div class="ribbon l-box-lrg pure-g" style="background: #2d3e50; color: #aaa; padding: 2em; border-bottom: 1px solid rgba(0,0,0,0.1);">
       <div class="l-box-lrg is-center pure-u-1 pure-u-md-1-2 pure-u-lg-2-5">
-        <img width="300" alt="A neon sign saying: Do something great!" class="pure-img-responsive" src="@pathPrefix/assets/img/clark-tibbs-oqStl2L5oxI-unsplash.jpg">
+        <img width="300" alt="A neon sign saying: Do something great!" class="pure-img-responsive" src="@{baseUri.addPath("assets/img/clark-tibbs-oqStl2L5oxI-unsplash.jpg")}">
       </div>
       <div class="pure-u-1 pure-u-md-1-2 pure-u-lg-3-5">
         <h2 class="content-head content-head-ribbon" style="color: white;">@Messages("landingpage.welcome.ribbon.title")</h2>
         <p>@Messages("landingpage.welcome.ribbon.text")</p>
-        <p><a class="pure-button pure-button-primary" href="/login">@Messages("global.login")</a></p>
+        <p><a class="pure-button pure-button-primary" href="@{baseUri.addPath("login")}">@Messages("global.login")</a></p>
       </div>
     </div>
   </div>