diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index e03bca3c5628123427a3b17e459f2dc0e5f0c37a..0b6d05c2932315795a5468033cddcb6b283475b5 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,178 +1,375 @@
-# Submit
-
-image: gradle:jdk17
+# Include templates for security scans and code quality reports
+include:
+  - template: Code-Quality.gitlab-ci.yml
+  - template: Security/Dependency-Scanning.gitlab-ci.yml
+  - template: Security/DAST.gitlab-ci.yml
+  - template: Security/SAST.gitlab-ci.yml
+  - template: Security/License-Scanning.gitlab-ci.yml
+  - template: Security/Secret-Detection.gitlab-ci.yml
+  - template: Verify/Accessibility.gitlab-ci.yml
+  - template: Verify/Browser-Performance.gitlab-ci.yml
 
+image: gitlab.ewi.tudelft.nl:4242/skill-circuits/docker-image:master
 
 variables:
   DOCKER_DRIVER: overlay2
   DOCKER_TLS_CERTDIR: "/certs"
-  GRADLE_USER_HOME: ".gradle"
-  GRADLE_OPTS: "-Dorg.gradle.daemon=false -Dorg.gradle.caching=true"
-  SAST_JAVA_VERSION: 11
 
+  SAST_DISABLE_DIND: "false"
+  SAST_DEFAULT_ANALYZERS: "spotbugs"
+
+  # Configure mysql environment variables
+  MYSQL_DATABASE: "$DB_NAME"
+  MYSQL_USER: "$DB_NAME"
+  MYSQL_PASSWORD: "$DB_PASSWORD"
+  MYSQL_ROOT_PASSWORD: "$DB_PASSWORD"
+
+  # Configure PostgreSQL environment variables
+  POSTGRES_DB: "$DB_NAME"
+  POSTGRES_USER: "$DB_USER"
+  POSTGRES_PASSWORD: "$DB_PASSWORD"
 
 # The names of the stages we use
 stages:
-  - build 1
-  - build 2
+  - build
+  - prepare
   - test
-  - review
-  - accessibility
-  - gitlab reports
   - publish
   - deploy
+  - review
+  - report
+  - live report
 
+  #
+  # Build
+  #
 
 # Default build cache settings to extend from
 .build_cached:
   cache:
     key: "gradle-build"
     paths:
+      # Only cache the gradle directory, as we do not use a shared cache
       - .gradle/
     policy: pull
-  rules:
-    - if: $CI_COMMIT_BRANCH == "master" ||
-        $CI_COMMIT_BRANCH == "development" ||
-        $CI_MERGE_REQUEST_ID ||
-        $CI_PIPELINE_SOURCE == "push"
 
-# Setting globally that gitlab reports need gradle_build job
 .gitlab_reporter:
-  stage: gitlab reports
   needs:
     - gradle_build
-
+  dependencies:
+    - gradle_build
+  allow_failure: true
 
 # Runs gradle build without tests or checks
 gradle_build:
   extends: .build_cached
+  stage: build
+  rules:
+    - if: $CI_COMMIT_BRANCH == "master" ||
+        $CI_COMMIT_BRANCH == "dev" ||
+        $CI_MERGE_REQUEST_ID ||
+        $CI_PIPELINE_SOURCE == "push" ||
+        $CI_PIPELINE_SOURCE == "trigger"
   cache:
     policy: pull-push
-  stage: build 1
   artifacts:
     name: build
     expire_in: 6 hours
     paths:
       - build/
   script:
-    - gradle --build-cache build testClasses -x test -x licenseMain -x licenseTest -x spotlessJava -x spotlessCheck
+    - gradle --build-cache build testClasses -x test -x licenseMain -x licenseTest -x spotlessJava -x spotlessFrontend -x spotlessCheck
+
 
+#
+# Test
+#
 
-# Generate the pom for dependency scanning
-generate_pom:
+# Run tests
+gradle_test:
   extends: .build_cached
-  stage: build 2
+  stage: test
   needs:
     - gradle_build
   rules:
-    - if: $CI_COMMIT_BRANCH == "master" ||
-        $CI_COMMIT_BRANCH == "development" ||
-        $CI_MERGE_REQUEST_ID
+    - if: $CI_PIPELINE_SOURCE == "trigger"
+      when: never
+    - if: $CI_COMMIT_BRANCH == "dev" ||
+        $CI_MERGE_REQUEST_ID ||
+        $CI_PIPELINE_SOURCE == "push"
+  coverage: '/Code coverage: \d+\.\d+/'
   artifacts:
-    name: pom
+    name: Coverage report
     expire_in: 6 hours
     paths:
-      - pom.xml
+      - codecov/
+    reports:
+      junit: build/test-results/test/TEST-*.xml
+      coverage_report:
+        coverage_format: cobertura
+        path: build/reports/jacoco/test/jacocoTestReport.xml
+  before_script:
+    - mv src/test/resources/application-h2.properties src/test/resources/application-test.properties
   script:
-    - gradle generatePomFileForGeneratePomPublication
+    - gradle --build-cache test
   after_script:
-    - cp build/publications/generatePom/pom-default.xml pom.xml
+    # Rerun with none of the dependent tasks to ensure creation of the report
+    # without having to recheck whether the code has compiled (it has in build cache).
+    - gradle jacocoTestReport -x processResources -x compileJava -x classes --rerun-tasks
 
+    # Print out the coverage percentage from the test report.
+    - awk -F"," '{ instructions += $4 + $5; covered += $5 } END { print covered, "/", instructions, " instructions covered"; print "Code coverage:", 100*covered/instructions }' build/reports/jacoco/test/jacocoTestReport.csv || true
+    - cp -r build/reports codecov
 
-# Run tests
-gradle_test:
+# Test the SQL migrations on Postgres and Mariadb
+mysql_migration:
+  extends: .build_cached
+  #  allow_failure: true
+  needs:
+    - gradle_build
+  dependencies:
+    - gradle_build
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "trigger"
+      when: never
+    - if: $CI_COMMIT_BRANCH == "master" ||
+        $CI_COMMIT_BRANCH == "dev" ||
+        $CI_MERGE_REQUEST_ID ||
+        $CI_PIPELINE_SOURCE == "push"
+  stage: prepare
+  services:
+    - mysql:latest
+  before_script:
+    - cp build/libs/submit-*.jar build/libs/submit.jar
+  script:
+    # Confirm that the migrations are syntactically valid for mysql
+    - java -cp build/libs/submit.jar -Dloader.system=true -Dloader.main=liquibase.integration.commandline.Main org.springframework.boot.loader.PropertiesLauncher --driver=com.mysql.cj.jdbc.Driver --changeLogFile=changelog-master.yaml --url="jdbc:mysql://mysql/$DB_NAME?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8" --username="$DB_USER" --password="$DB_PASSWORD" --logLevel=debug update
+
+mysql_cross_migration:
+  extends: .build_cached
+  allow_failure: true
+  needs:
+    - gradle_build
+  dependencies:
+    - gradle_build
+  rules:
+    - if: $CI_MERGE_REQUEST_ID
+  stage: prepare
+  services:
+    - mysql:latest
+  before_script:
+    - mv src/test/resources/application-mysql.properties src/test/resources/application-test.properties
+
+    - sed -i "s/__db_name__/$DB_NAME/g" ./src/test/resources/application-test.properties
+    - sed -i "s/__username__/$DB_USER/g" ./src/test/resources/application-test.properties
+    - sed -i "s/__password__/$DB_PASSWORD/g" ./src/test/resources/application-test.properties
+  script:
+    # Run no more of the migration tests if this is not a merge request run.
+    - if [ -z $CI_MERGE_REQUEST_TARGET_BRANCH_NAME ]; then exit 0; fi
+    - chmod +x ./scripts/migration-cross-branch-test.sh
+    - ./scripts/migration-cross-branch-test.sh $CI_MERGE_REQUEST_TARGET_BRANCH_NAME
+
+postgreSQL_migration:
+  extends: .build_cached
+  needs:
+    - gradle_build
+  dependencies:
+    - gradle_build
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "trigger"
+      when: never
+    - if: $CI_COMMIT_BRANCH == "master" ||
+        $CI_COMMIT_BRANCH == "dev" ||
+        $CI_MERGE_REQUEST_ID ||
+        $CI_PIPELINE_SOURCE == "push"
+  stage: prepare
+  services:
+    - postgres:latest
+  before_script:
+    - cp build/libs/submit-*.jar build/libs/submit.jar
+  script:
+    # Confirm that the migrations are syntactically valid for postgresql
+    - java -cp build/libs/submit.jar -Dloader.system=true -Dloader.main=liquibase.integration.commandline.Main org.springframework.boot.loader.PropertiesLauncher --driver=org.postgresql.Driver --changeLogFile=changelog-master.yaml --url="jdbc:postgresql://postgres/$DB_NAME" --username="$DB_USER" --password="$DB_PASSWORD" --logLevel=debug update
+
+postgreSQL_cross_migration:
+  extends: .build_cached
+  allow_failure: true
+  needs:
+    - gradle_build
+  dependencies:
+    - gradle_build
+  rules:
+    - if: $CI_MERGE_REQUEST_ID
+  stage: prepare
+  services:
+    - postgres:latest
+  before_script:
+    - mv src/test/resources/application-postgresql.properties src/test/resources/application-test.properties
+
+    - sed -i "s/__db_name__/$DB_NAME/g" ./src/test/resources/application-test.properties
+    - sed -i "s/__username__/$DB_USER/g" ./src/test/resources/application-test.properties
+    - sed -i "s/__password__/$DB_PASSWORD/g" ./src/test/resources/application-test.properties
+  script:
+    # Run no more of the migration tests if this is not a merge request run.
+    - if [ -z $CI_MERGE_REQUEST_TARGET_BRANCH_NAME ]; then exit 0; fi
+    - chmod +x ./scripts/migration-cross-branch-test.sh
+    - ./scripts/migration-cross-branch-test.sh $CI_MERGE_REQUEST_TARGET_BRANCH_NAME
+
+# Run all tests on a mysql database and on a postgresql database
+mysql_test:
   extends: .build_cached
   stage: test
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "trigger" ||
+        $CI_MERGE_REQUEST_EVENT_TYPE == "merge_train"
+      when: never
+
+    # Run on development and master branches.
+    - if: $CI_COMMIT_BRANCH == "master" ||
+        $CI_COMMIT_BRANCH == "dev"
+
+    # Only run on merge requests that change one of the model/migration files otherwise.
+    - if: $CI_PIPELINE_SOURCE != "merge_request_event"
+      when: never
+
+    - changes:
+        - src/main/java/nl/tudelft/submit/model/**/*
+        - .gitlab-ci.yml
+        - src/test/resources/*
+        - src/main/resources/changelog-master.yaml
+        - src/main/resources/table-setup.yaml
+        - src/main/resources/migrations.yaml
   needs:
+    - mysql_migration
     - gradle_build
-  coverage: '/Code coverage: \d+\.\d+/'
+  dependencies:
+    - gradle_build
+  services:
+    - mysql:latest
   artifacts:
-     name: Coverage report
-     expire_in: 6 hours
-     paths:
-       - codecov/
-     reports:
-       junit: build/test-results/test/TEST-*.xml
-       coverage_report:
-         coverage_format: cobertura
-         path: build/reports/jacoco/test/jacocoTestReport.xml
+    reports:
+      junit: build/test-results/test/TEST-*.xml
+  before_script:
+    - mv src/test/resources/application-mysql.properties src/test/resources/application-test.properties
+
+    - sed -i "s/__db_name__/$DB_NAME/g" ./src/test/resources/application-test.properties
+    - sed -i "s/__username__/$DB_USER/g" ./src/test/resources/application-test.properties
+    - sed -i "s/__password__/$DB_PASSWORD/g" ./src/test/resources/application-test.properties
   script:
     - gradle --build-cache test
-  after_script:
-     # Rerun with none of the dependent tasks to ensure creation of the report
-     # without having to recheck whether the code has compiled (it has in build cache).
-     - gradle jacocoTestReport -x processResources -x compileJava -x classes --rerun-tasks
 
-     # Print out the coverage percentage from the test report.
-     - awk -F"," '{ instructions += $4 + $5; covered += $5 } END { print covered, "/", instructions, " instructions covered"; print "Code coverage:", 100*covered/       instructions }' build/reports/jacoco/test/jacocoTestReport.csv || true
-     - cp -r build/reports codecov
+# disabled until someone has time to debug
+#postgreSQL_test:
+#  extends: .build_cached
+#  stage: test
+#  rules:
+#    - if: $CI_PIPELINE_SOURCE == "trigger" ||
+#        $CI_MERGE_REQUEST_EVENT_TYPE == "merge_train"
+#      when: never
+#
+#    # Always run on development and master branches.
+#    - if: $CI_COMMIT_BRANCH == "master" ||
+#        $CI_COMMIT_BRANCH == "dev"
+#
+#    # Only run on merge requests that change one of the model/migration files otherwise.
+#    - if: $CI_PIPELINE_SOURCE != "merge_request_event"
+#      when: never
+#
+#    - changes:
+#        - src/main/java/nl/tudelft/submit/model/**/*
+#        - .gitlab-ci.yml
+#        - src/test/resources/*
+#        - src/main/resources/changelog-master.yaml
+#        - src/main/resources/table-setup.yaml
+#        - src/main/resources/migrations.yaml
+#      when: always
+#  needs:
+#    - postgreSQL_migration
+#    - gradle_build
+#  dependencies:
+#    - gradle_build
+#  services:
+#    - postgres:latest
+#  artifacts:
+#    reports:
+#      junit: build/test-results/test/TEST-*.xml
+#  before_script:
+#    - 'mv src/test/resources/application-postgresql.properties src/test/resources/application-test.properties'
+#
+#    - 'sed -i "s/__db_name__/$DB_NAME/g" ./src/test/resources/application-test.properties'
+#    - 'sed -i "s/__username__/$DB_USER/g" ./src/test/resources/application-test.properties'
+#    - 'sed -i "s/__password__/$DB_PASSWORD/g" ./src/test/resources/application-test.properties'
+#  script:
+#    - gradle --build-cache test
 
 
+#
+# Review
+#
+
 # Run spotless
 gradle_spotless:
   extends: .build_cached
-  stage: review
   needs:
     - gradle_build
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "trigger"
+      when: never
+    - if: $CI_COMMIT_BRANCH == "master" ||
+        $CI_COMMIT_BRANCH == "dev" ||
+        $CI_MERGE_REQUEST_ID ||
+        $CI_PIPELINE_SOURCE == "push"
   artifacts:
     name: spotless
     expose_as: Spotless Diagnosis
     expire_in: 7 days
     paths:
       - spotless-diagnose-java/
+      - spotless-diagnose-frontend/
+  stage: review
   script:
-    - gradle --build-cache spotlessCheck
+    - gradle spotlessCheck
   after_script:
     - cp -r build/spotless-diagnose-java spotless-diagnose-java/
-
+    - cp -r build/spotless-diagnose-frontend spotless-diagnose-frontend/
 
 # Run license check
 gradle_licenses:
   extends: .build_cached
-  stage: review
   needs:
     - gradle_build
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "trigger"
+      when: never
+    - if: $CI_COMMIT_BRANCH == "master" ||
+        $CI_COMMIT_BRANCH == "dev" ||
+        $CI_MERGE_REQUEST_ID ||
+        $CI_PIPELINE_SOURCE == "push"
+  stage: review
   script:
-    - gradle --build-cache licenseMain
-    - gradle --build-cache licenseTest
+    - gradle licenseMain
+    - gradle licenseTest
 
 
-# Publish jacoco test report
-publish_jacoco_report:
-  extends: .build_cached
-  stage: publish
-  needs:
-    - gradle_test
-  coverage: '/Code coverage: \d+\.\d+/'
-  artifacts:
-    name: codecov
-    expose_as: Code coverage report
-    expire_in: 7 days
-    paths:
-      - codecov/
-  script:
-    # Rerun with none of the dependent tasks to ensure creation of the report
-    # without having to recheck whether the code has compiled (it has in build cache).
-    - gradle jacocoTestReport -x processResources -x compileJava -x classes --rerun-tasks
-  after_script:
-    # Print out the coverage percentage from the test report.
-    - awk -F"," '{ instructions += $4 + $5; covered += $5 } END { print covered, "/", instructions, " instructions covered"; print "Code coverage:", 100*covered/instructions }' build/reports/jacoco/test/jacocoTestReport.csv
-    - cp -r build/reports codecov
+#
+# Publish
+#
 
-
-# Publish the JAR for Submit
+# Publish the JAR for TAM
 publish_jar:
   extends: .build_cached
   stage: publish
   rules:
-     - if: $CI_COMMIT_BRANCH == "master" ||
-         $CI_COMMIT_BRANCH == "development" ||
-         $CI_MERGE_REQUEST_ID ||
-         $CI_PIPELINE_SOURCE == "push" ||
-         $CI_PIPELINE_SOURCE == "trigger"
+    - if: $CI_COMMIT_BRANCH == "master" ||
+        $CI_COMMIT_BRANCH == "dev" ||
+        $CI_MERGE_REQUEST_ID ||
+        $CI_PIPELINE_SOURCE == "push" ||
+        $CI_PIPELINE_SOURCE == "trigger"
+  needs:
+    - gradle_build
   artifacts:
-    name: submit
-    expose_as: Submit JAR
+    name: ta
+    expose_as: TAM JAR
     expire_in: 7 days
     paths:
       - submit.jar
@@ -180,130 +377,9 @@ publish_jar:
     - cp build/libs/submit-*.jar ./submit.jar
 
 
-# Include templates for security scans and code quality reports
-include:
-  - template: Dependency-Scanning.gitlab-ci.yml
-  - template: Jobs/Code-Quality.gitlab-ci.yml
-  - template: Security/DAST.gitlab-ci.yml
-  - template: Security/SAST.gitlab-ci.yml
-  - template: Security/License-Scanning.gitlab-ci.yml
-  - template: Security/Secret-Detection.gitlab-ci.yml
-  - template: Verify/Accessibility.gitlab-ci.yml
-    #  - template: Verify/Browser-Performance.gitlab-ci.yml
-
-
-# Runs the code quality reporter
-code_quality:
-  extends:
-    - .gitlab_reporter
-  stage: gitlab reports
-  rules:
-    - if: $CI_COMMIT_BRANCH == "master" ||
-        $CI_COMMIT_BRANCH == "development" ||
-        $CI_MERGE_REQUEST_ID
-
-# Runs the SAST reporter manually
-# (there was a problem with running this from the template with Java 11,
-#  even though it should have been configured)
-#sast:
-#  extends:
-#    - .build_cached
-#    - .gitlab_reporter
-#  stage: gitlab reports
-#  rules:
-#    - if: $CI_COMMIT_BRANCH == "master" ||
-#        $CI_COMMIT_BRANCH == "development" ||
-#        $CI_MERGE_REQUEST_ID
-#      when: manual
-#  allow_failure: true
-
-
-# Run the DAST security checks and reporter.
-# Currently set to manual as it requires a test environment to be up and running.
-dast:
-  extends:
-    - .build_cached
-    - .gitlab_reporter
-  stage: gitlab reports
-  rules:
-    - if: $CI_COMMIT_BRANCH == "master" ||
-        $CI_COMMIT_BRANCH == "development" ||
-        $CI_MERGE_REQUEST_ID
-      when: manual
-  variables:
-    DAST_VERSION: latest
-
-# Accessibility testing
-a11y:
-  extends:
-    - .gitlab_reporter
-  rules:
-    - if: $CI_PIPELINE_SOURCE == "trigger" ||
-        $CI_MERGE_REQUEST_EVENT_TYPE == "merge_train"
-      when: never
-    - if: $CI_COMMIT_BRANCH == "master" ||
-        $CI_COMMIT_BRANCH == "development"
-      when: manual
-  stage: gitlab reports
-  variables:
-    ally_urls: "https://submit.tudelft.nl"
-
-# Accessibility testing
-#performance:
-#  extends:
-#    - .gitlab_reporter
-#  rules:
-#    - if: $CI_PIPELINE_SOURCE == "trigger" ||
-#        $CI_MERGE_REQUEST_EVENT_TYPE == "merge_train"
-#      when: never
-#    - if: $CI_COMMIT_BRANCH == "master" ||
-#        $CI_COMMIT_BRANCH == "development"
-#      when: manual
-#  stage: gitlab reports
-#  variables:
-#    URL: "https://submit.tudelft.nl"
-
-# Dependency scanning reporter for checking dependencies of Queue.
-#dependency_scanning:
-#  extends:
-#    - .build_cached
-#    - .gitlab_reporter
-#  stage: gitlab reports
-#  rules:
-#    - if: $CI_COMMIT_BRANCH == "master" ||
-#        $CI_COMMIT_BRANCH == "development" ||
-#        $CI_MERGE_REQUEST_ID
-#  needs:
-#    - generate_pom
-#  dependencies:
-#    - generate_pom
-#  before_script:
-#    - rm build.gradle* gradlew gradlew.bat
-#    - cat pom.xml
-#  variables:
-#    MAVEN_CLI_OPTS: -q -Dmaven.main.skip -Dmaven.test.skip -DskipTests --batch-mode
-
-
-# License scanning reporter for checking the licenses of dependencies.
-license_scanning:
-  extends:
-    - .build_cached
-    - .gitlab_reporter
-  stage: gitlab reports
-  rules:
-    - if: $CI_COMMIT_BRANCH == "master" ||
-        $CI_COMMIT_BRANCH == "development" ||
-        $CI_MERGE_REQUEST_ID
-  needs:
-    - generate_pom
-  dependencies:
-    - generate_pom
-  before_script:
-    - rm build.gradle* gradlew gradlew.bat
-    - cat pom.xml
-  variables:
-    MAVEN_CLI_OPTS: -q -Dmaven.main.skip -Dmaven.test.skip -DskipTests --batch-mode
-    LM_JAVA_VERSION: 11
+#
+# Deploy
+#
 
 # job for deploying on staging
 deploy_staging:
@@ -331,6 +407,7 @@ deploy_staging:
       when: never
     - if: $CI_COMMIT_BRANCH == "development"
 
+# Manual job for deploying Submit on submit.tudelft.nl
 deploy:
   image: getsentry/sentry-cli
   stage: deploy
@@ -361,14 +438,14 @@ deploy:
     - sentry-cli releases set-commits $SENTRY_RELEASE_VERSION --auto
     - sentry-cli releases new $SENTRY_RELEASE_VERSION
     - ssh deploy@submit.tudelft.nl cp /var/www/submit/submit.jar /var/www/submit/submit.jar.bak
-    - scp submit.jar deploy@submit.tudelft.nl:/var/www/submit/
+    - scp submit.jar deploy@submit.tudelft.nl:/var/www/submit
     - ssh deploy@submit.tudelft.nl sudo /bin/systemctl restart submit
     - sentry-cli releases finalize $SENTRY_RELEASE_VERSION
     - now=$(date +%s)
     - sentry-cli releases deploys $SENTRY_RELEASE_VERSION new -e production -t $((now-start))
   environment:
     name: production
-    url: https://submit.tudelft.nl
+    url: https://ta.ewi.tudelft.nl
   rules:
     - if: $CI_PIPELINE_SOURCE == "trigger" ||
         $CI_MERGE_REQUEST_EVENT_TYPE == "merge_train"
@@ -376,3 +453,137 @@ deploy:
     - if: $CI_COMMIT_BRANCH == "master"
       when: manual
 
+
+#
+# Gitlab Reports
+#
+
+# Runs the code quality reporter
+code_quality:
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "trigger" ||
+        $CI_MERGE_REQUEST_EVENT_TYPE == "merge_train"
+      when: never
+    - if: $CI_COMMIT_BRANCH == "master" ||
+        $CI_COMMIT_BRANCH == "dev" ||
+        $CI_MERGE_REQUEST_ID
+  stage: report
+
+# Runs the SAST checks and reporter.
+spotbugs-sast:
+  variables:
+    COMPILE: "false"
+  allow_failure: true
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "trigger" ||
+        $CI_MERGE_REQUEST_EVENT_TYPE == "merge_train"
+      when: never
+    - if: $CI_COMMIT_BRANCH == "master" ||
+        $CI_COMMIT_BRANCH == "dev" ||
+        $CI_MERGE_REQUEST_ID
+  stage: report
+  needs:
+    - gradle_build
+  dependencies:
+    - gradle_build
+
+# Run the DAST security checks and reporter.
+# Currently set to manual as it requires a test environment to be up and running.
+dast:
+  extends:
+    - .build_cached
+    - .gitlab_reporter
+  tags:
+    - longJob
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "trigger" ||
+        $CI_MERGE_REQUEST_EVENT_TYPE == "merge_train"
+      when: never
+    - if: $CI_COMMIT_BRANCH == "dev"
+      when: manual
+  stage: live report
+  variables:
+    DAST_VERSION: latest
+
+# Run the secret detection security check and reporter.
+secret_detection:
+  extends:
+    - .build_cached
+    - .gitlab_reporter
+  image: "$SECURE_ANALYZERS_PREFIX/secrets:$SECRETS_ANALYZER_VERSION"
+  variables:
+    SECRET_DETECTION_HISTORIC_SCAN: "true"
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "trigger" ||
+        $CI_MERGE_REQUEST_EVENT_TYPE == "merge_train"
+      when: never
+    - if: $CI_COMMIT_BRANCH == "master" ||
+        $CI_COMMIT_BRANCH == "dev" ||
+        $CI_MERGE_REQUEST_ID
+  stage: report
+
+# Dependency scanning reporter for checking dependencies of TAM.
+dependency_scanning:
+  stage: report
+
+gemnasium-dependency_scanning:
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "trigger" ||
+        $CI_MERGE_REQUEST_EVENT_TYPE == "merge_train"
+      when: never
+    - if: $CI_COMMIT_BRANCH == "master" ||
+        $CI_COMMIT_BRANCH == "dev" ||
+        $CI_MERGE_REQUEST_ID
+  needs:
+    - gradle_build
+  dependencies:
+    - gradle_build
+
+# License scanning reporter for checking the licenses of dependencies.
+license_scanning:
+  extends:
+    - .build_cached
+    - .gitlab_reporter
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "trigger" ||
+        $CI_MERGE_REQUEST_EVENT_TYPE == "merge_train"
+      when: never
+    - if: $CI_COMMIT_BRANCH == "master" ||
+        $CI_COMMIT_BRANCH == "dev" ||
+        $CI_MERGE_REQUEST_ID
+  stage: report
+  variables:
+    LM_JAVA_VERSION: 17
+
+# Accessibility testing
+a11y:
+  extends:
+    - .gitlab_reporter
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "trigger" ||
+        $CI_MERGE_REQUEST_EVENT_TYPE == "merge_train"
+      when: never
+    - if: $CI_COMMIT_BRANCH == "dev"
+  stage: live report
+  variables:
+    ally_urls: "https://submit.eiptest.ewi.tudelft.nl"
+  needs:
+    - job: deploy_staging
+    - job: gradle_build
+
+# Accessibility testing
+browser_performance:
+  extends:
+    - .gitlab_reporter
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "trigger" ||
+        $CI_MERGE_REQUEST_EVENT_TYPE == "merge_train"
+      when: never
+    - if: $CI_COMMIT_BRANCH == "dev"
+  stage: live report
+  variables:
+    URL: "https://submit.eiptest.ewi.tudelft.nl"
+  needs:
+    - job: deploy_staging
+    - job: gradle_build
+
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 56df2b9f2f62864bb19cf735c3a484dad7308174..3e158a870fc2e182b79b6919f6cf56c3891b203f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -27,11 +27,11 @@ In order to get the project up and running, there is a few steps you need to tak
 1. Install [IntelliJ IDEA Professional](https://www.jetbrains.com/idea/) which is free for students.
 1. Clone the project into your local directory (`git clone <LINK_TO_GIT_REPOSITORY>`).
 1. Import the project into IntelliJ using the `build.gradle.kts` file. Instructions can be found [here](https://www.jetbrains.com/help/idea/gradle.html#). Please note that we use the Kotlin version of Gradle, however, the steps remain exactly the same.
-1. Make sure that you are running [JDK 11](https://jdk.java.net/archive/), otherwise, IntelliJ won't run the project. Instructions on how to set this up can be found [here](https://www.jetbrains.com/help/idea/sdk.html#).
+1. Make sure that you are running [JDK 17](https://jdk.java.net/archive/) or a newer version, to avoid any unexpected behaviour in IntelliJ. Instructions on how to set this up can be found [here](https://www.jetbrains.com/help/idea/sdk.html#).
 1. Setup the application context.
     1. Copy the contents of the `application.template.yaml` file into a new file named `application.yaml`. This file is ignored by git and so any changes you make to it will not be pushed to remote.
     1. For development purposes, set the `labrador.sso.type` property to `in_memory` (for testing SAML Login, this property needs to be set to `saml` and `saml.sso.key-manager.store-pass` and `saml.sso.key-manager.key-passwords.1` need to be provided with the correct password).
-    1. You need to provide data to all the `spring.mail` properties with the `{{<PROPERTY>}}` format. If you do not need mailing, this data can be bogus but if you want to contribute to a feature requires mailing, you need to provide it with valid credentials.
+    1. You need to provide data to all the `spring.mail` properties which currently have the `{{<PROPERTY>}}` format. If you do not need mailing, this data can be bogus, but if you want to contribute to a feature that requires mailing, you need to provide it with valid credentials. In any case, make sure you remove the curly braces from said properties.
     1. Make sure the `spring.profiles.active` property is set to `dev`.
 1. Repeat the previous steps for the LabraCORE project (the `spring.profiles.active` needs to be set to `development`). You need to have both projects open in order to be able to run Submit.
 1. To run the application:
@@ -96,6 +96,6 @@ There are a few things we'd like to ask you to do, to keep the project as organi
   * Log changes under the `[UNRELEASED]` tag.
   * Make sure to put it under the correct category (`added` / `changed` / `fixed`).
 * **Make sure the code is properly formatted.** You can check you formatting by running `./gradlew spotlessCheck` and have it automatically formatted by running `./gradlew spotlessApply`.
-* **Test you code.** Make sure you test all the code you write. We try to keep the test coverage as high as possible. To run the tests with gradle, you can run `./gradlew test` and to generate the reports, you can run `./gradlew jacocoTestReport` or `./gradlew jacocoTestCoverageVerification`.
+* **Test your code.** Make sure you test all the code you write. We try to keep the test coverage as high as possible. To run the tests with gradle, you can run `./gradlew test` and to generate the reports, you can run `./gradlew jacocoTestReport` or `./gradlew jacocoTestCoverageVerification`.
 * **Ensure your GitLab CI passes.** Your MR will not be able to merge if the pipelines on your MR don't pass. To check everything locally, run `./gradlew clean` followed by `./gradlew build`. Sometimes, one of the jobs in the CI doesn't pass due to a timeout - this might have been cause by a high load on the GitLab builders, so simply rerunning that job will often fix the issue.
 * **Write detailed issue and MR descriptions.** There are templates created for both issues and merge requests which you can fill in to keep your description as clear and informative as possible.
diff --git a/build.gradle.kts b/build.gradle.kts
index 61d98fbcc73e11959fa317e3c7a9b89b3b9fb476..13d280eb72a74443e05a625cdc819d59b19a15d7 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -9,9 +9,10 @@ version = "2.1.0"
 val javaVersion = JavaVersion.VERSION_17
 
 val labradoorVersion = "1.3.2-SNAPSHOT"
-val libradorVersion = "1.0.3-SNAPSHOT7"
+val libradorVersion = "1.2.2-SNAPSHOT"
+val chihuahUIVersion = "1.0.0"
 val samlVersion = "1.17"
-val springBootVersion: String = "2.4.4"
+val springBootVersion = "2.4.4"
 val hibernateVersion = "5.4.30.Final"
 val lombokVersion = "1.18.18"
 val springDocVersion = "1.2.32"
@@ -41,6 +42,9 @@ repositories {
     maven {
         url = uri("https://gitlab.ewi.tudelft.nl/api/v4/projects/3634/packages/maven")
     }
+    maven {
+        url = uri("https://gitlab.ewi.tudelft.nl/api/v4/projects/8633/packages/maven")
+    }
 }
 
 // The plugins used by Gradle to generate files, start Spring boot, perform static analysis etc.
@@ -57,12 +61,12 @@ plugins {
 
     // Spring plugins for managing dependencies and creating
     // a nice Spring Boot application.
-    id("org.springframework.boot").version("2.5.6")
-    id("io.spring.dependency-management").version("1.0.11.RELEASE")
+    id("org.springframework.boot").version("2.7.3")
+    id("io.spring.dependency-management").version("1.0.13.RELEASE")
 	id("com.github.ben-manes.versions").version("0.39.0")
 
     // Spotless plugin for checking style of Java code.
-    id("com.diffplug.spotless").version("6.0.1")
+    id("com.diffplug.spotless").version("6.10.0")
 
     // Plugin for checking license headers within our code and files.
     id("com.github.hierynomus.license").version("0.16.1")
@@ -124,6 +128,17 @@ configure<LicenseExtension> {
 
 // Configure Spotless plugin for style checking Java code.
 configure<SpotlessExtension> {
+    format("frontend") {
+        target("src/main/resources/**/*.html", "src/main/resources/**/*.js", "src/main/resources/scss/**/*.scss")
+
+        prettier("2.6").config(mapOf(
+            "tabWidth" to 4, "semi" to true,
+            "printWidth" to 100,
+            "bracketSameLine" to true,
+            "arrowParens" to "avoid",
+            "htmlWhitespaceSensitivity" to "ignore"))
+    }
+
     java {
         // Use the eclipse formatter format and import order.
         eclipse().configFile(file("eclipse-formatter.xml"))
@@ -219,6 +234,7 @@ dependencies {
     implementation("nl.tudelft.labrador:librador:$libradorVersion") {
         exclude("org.springframework.boot", "spring-boot-devtools")
     }
+    implementation("nl.tudelft.labrador:chihuahui:$chihuahUIVersion")
 
     // Guava
     implementation("com.google.guava:guava:11.0.2")
@@ -271,7 +287,7 @@ dependencies {
     implementation("org.webjars:js-cookie:2.2.1")
     implementation("org.webjars:sockjs-client:1.5.1")
     implementation("org.webjars:stomp-websocket:2.3.4")
-    implementation("org.webjars:font-awesome:5.15.3")
+    implementation("org.webjars:font-awesome:6.4.0")
     implementation("org.webjars:chartjs:26962ce-1")
     implementation("org.webjars:dropzone:5.7.2")
 
diff --git a/scripts/migration-cross-branch-test.sh b/scripts/migration-cross-branch-test.sh
new file mode 100755
index 0000000000000000000000000000000000000000..fcd6e459d933229ecf6dde0b76f0e25ab4d0cc11
--- /dev/null
+++ b/scripts/migration-cross-branch-test.sh
@@ -0,0 +1,56 @@
+#!/usr/bin/env sh
+THIS_FILE=$0
+TARGET_BRANCH=$1
+
+if [ -z "$TARGET_BRANCH" ]; then
+  echo "No target branch provided, not running branch-to-branch migration test."
+  echo "Please run this script again as: './scripts/migration-cross-branch-test.sh <BRANCH_NAME>'"
+  exit 0
+fi
+
+# Stash any current changes to later unstash
+CURRENT_STASHES=$(git stash list | wc -l)
+git stash
+NEW_STASHES=$(git stash list | wc -l)
+
+if [ "$CURRENT_STASHES" != "$NEW_STASHES" ]; then
+  echo "Successfully stashed your current changes"
+  STASHED="yes"
+fi
+
+# Run migration test with DB population on target branch, this should populate the DB and leave it for the next run
+git checkout "$TARGET_BRANCH"
+git pull
+
+# Stupid self-check to see whether this is not the first time this script is introduced
+if [ ! -f "$THIS_FILE" ]; then
+  echo "Target branch does not contain script for migrations, assuming this is the first cross-branch run."
+  git checkout -
+
+  if [ -n "$STASHED" ]; then
+    git stash pop
+  fi
+
+  exit 0
+fi
+
+./gradlew clean
+./gradlew test --tests "*.MigrationApplicationTest" -i -x generateGitProperties
+EXIT_CODE=$?
+
+git checkout -
+if [ -n "$STASHED" ]; then
+  git stash pop
+fi
+
+if [ ! $EXIT_CODE -eq 0 ]; then
+  echo "Non-zero exit code from migration test, returning"
+  exit 1
+fi
+
+# Then run the migration test on current branch, but without loading a new database to see if data transfer is okay
+
+./gradlew clean
+./gradlew test --tests "*.NoDbMigrationApplicationTest" -i -x generateGitProperties
+exit $?
+
diff --git a/scripts/migration-mysql-test.sh b/scripts/migration-mysql-test.sh
new file mode 100644
index 0000000000000000000000000000000000000000..2723a36339a51fd831cd7e8695173a991f3ea21b
--- /dev/null
+++ b/scripts/migration-mysql-test.sh
@@ -0,0 +1,13 @@
+#!/usr/bin/env sh
+DB_NAME="submit"
+DB_USER="submit"
+DB_PASSWORD="password"
+
+java -cp build/libs/submit.jar -Dloader.system=true -Dloader.main=liquibase.integration.commandline.Main org.springframework.boot.loader.PropertiesLauncher\
+        --driver=com.mysql.cj.jdbc.Driver\
+        --changeLogFile=changelog-master.yaml\
+        --url="jdbc:mysql://mysql/$DB_NAME?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8"\
+        --username="$DB_USER"\
+        --password="$DB_PASSWORD"\
+        --logLevel=debug\
+          update
\ No newline at end of file
diff --git a/scripts/migration-postgres-test.sh b/scripts/migration-postgres-test.sh
new file mode 100644
index 0000000000000000000000000000000000000000..1572e4d5be63d2bc56348844e05111085869ea06
--- /dev/null
+++ b/scripts/migration-postgres-test.sh
@@ -0,0 +1,13 @@
+#!/usr/bin/env sh
+DB_NAME="submit"
+DB_USER="submit"
+DB_PASSWORD="password"
+
+java -cp build/libs/submit.jar -Dloader.system=true -Dloader.main=liquibase.integration.commandline.Main org.springframework.boot.loader.PropertiesLauncher\
+        --driver=org.postgresql.Driver\
+        --changeLogFile=table-setup.yaml\
+        --url="jdbc:postgresql://postgres/$DB_NAME"\
+        --username="$DB_USER"\
+        --password="$DB_PASSWORD"\
+        --logLevel=debug\
+          update
\ No newline at end of file
diff --git a/src/main/java/nl/tudelft/submit/controller/AssignmentController.java b/src/main/java/nl/tudelft/submit/controller/AssignmentController.java
index c53449b4e8508cc45588fbfc1eaf54c463fa5955..9301fb2867afe37584b79a2088f4a723f637d199 100644
--- a/src/main/java/nl/tudelft/submit/controller/AssignmentController.java
+++ b/src/main/java/nl/tudelft/submit/controller/AssignmentController.java
@@ -51,7 +51,6 @@ import nl.tudelft.submit.dto.view.FeedbackViewDTO;
 import nl.tudelft.submit.dto.view.VersionViewDTO;
 import nl.tudelft.submit.dto.view.labracore.SubmitAssignmentDetailsDTO;
 import nl.tudelft.submit.dto.view.labracore.SubmitSubmissionViewDTO;
-import nl.tudelft.submit.enums.GradeStatistics;
 import nl.tudelft.submit.model.Signature;
 import nl.tudelft.submit.security.AuthorizationService;
 import nl.tudelft.submit.service.*;
@@ -210,6 +209,7 @@ public class AssignmentController {
 		model.addAttribute("grades", submissions.stream()
 				.flatMap(s -> s.getGrades().stream())
 				.collect(Collectors.toMap(GradeSummaryDTO::getId, Function.identity())));
+		model.addAttribute("versions", versionService.getVersionsPerAssignment(id));
 
 		return "assignment/submissions";
 	}
@@ -226,37 +226,14 @@ public class AssignmentController {
 	public String getStatistics(@PathVariable Long id, Model model) {
 		AssignmentModuleDetailsDTO assignment = assignmentService.getAssignmentModuleDetails(id);
 
-		model.addAttribute("assignment", assignment);
+		model.addAttribute("assignment", assignmentService.getSubmitAssignmentDetails(id));
 		model.addAttribute("edition", editionService.getEdition(assignment.getModule().getEdition().getId()));
 		model.addAttribute("statistics", statisticsService.getAssignmentStatistics(assignment));
+		model.addAttribute("versions", versionService.getVersionsPerAssignment(id));
 
 		return "assignment/statistics";
 	}
 
-	/**
-	 * Gets the statistics table page for an assignment.
-	 *
-	 * @param  id    The id of the assignment
-	 * @param  model The model to add details to
-	 * @return       The page to be loaded
-	 */
-	@GetMapping("/{id}/statistics-table")
-	@PreAuthorize("@authorizationService.canViewAssignmentStatistics(#id)")
-	public String getStatisticsTable(@PathVariable Long id, @RequestParam(required = false) String q,
-			@RequestParam(required = false) String status,
-			@RequestParam(required = false) Long version, Model model) {
-		AssignmentModuleDetailsDTO assignment = assignmentService.getAssignmentModuleDetails(id);
-
-		model.addAttribute("assignment", assignment);
-		model.addAttribute("edition", editionService.getEdition(assignment.getModule().getEdition().getId()));
-		model.addAttribute("data", statisticsService.getAssignmentTableData(assignment, q,
-				status == null || status.equals("all") ? null : GradeStatistics.valueOf(status),
-				Long.valueOf(-1L).equals(version) ? null : version));
-		model.addAttribute("versions", versionService.getVersionsPerAssignment(assignment.getId()));
-
-		return "assignment/statistics_table";
-	}
-
 	/**
 	 * Adds an assignment by posting an add request to the Labracore API.
 	 *
diff --git a/src/main/java/nl/tudelft/submit/controller/EditionController.java b/src/main/java/nl/tudelft/submit/controller/EditionController.java
index cd764eeb4d403d0e231212a5bbb030c5a3335e6f..bf14d399609a49db16b35088a6f9e44ce998a850 100644
--- a/src/main/java/nl/tudelft/submit/controller/EditionController.java
+++ b/src/main/java/nl/tudelft/submit/controller/EditionController.java
@@ -22,6 +22,7 @@ import java.io.InputStreamReader;
 import java.nio.charset.StandardCharsets;
 import java.time.LocalDateTime;
 import java.util.*;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 
 import javax.transaction.Transactional;
@@ -40,12 +41,8 @@ import nl.tudelft.submit.dto.patch.AddMemberDTO;
 import nl.tudelft.submit.dto.patch.EditionGradingPatchDTO;
 import nl.tudelft.submit.dto.patch.EditionParticipantImportDTO;
 import nl.tudelft.submit.dto.patch.SubmitEditionPatchDTO;
-import nl.tudelft.submit.dto.view.labracore.MemberViewDTO;
-import nl.tudelft.submit.dto.view.labracore.SubmitAssignmentSummaryDTO;
-import nl.tudelft.submit.dto.view.labracore.SubmitEditionViewDTO;
-import nl.tudelft.submit.dto.view.labracore.SubmitModuleViewDTO;
+import nl.tudelft.submit.dto.view.labracore.*;
 import nl.tudelft.submit.dto.view.statistics.EditionTableRowDTO;
-import nl.tudelft.submit.enums.GradeStatistics;
 import nl.tudelft.submit.security.AuthorizationService;
 import nl.tudelft.submit.service.*;
 
@@ -180,38 +177,115 @@ public class EditionController {
 	 * @return       The page to be loaded
 	 */
 	@GetMapping("/{id}")
-	@PreAuthorize("@authorizationService.canViewEdition(#id.id)")
-	public String getEditionDetails(@AuthenticatedPerson Person person, @PathVariable EditionIdDTO id,
+	@PreAuthorize("@authorizationService.canViewEdition(#id)")
+	public String getEditionDetails(@AuthenticatedPerson Person person, @PathVariable Long id,
 			Model model) {
-		boolean isStudent = authorizationService.isStudentInEdition(id.getId());
-
-		SubmitEditionViewDTO edition = editionService.getDetailedEditionView(id);
-
-		if (isStudent) {
-			model.addAttribute("grade",
-					gradeService
-							.getEditionScore(new PersonIdDTO().id(person.getId()), id)
-							.map(gradeService::getScoreDisplayString).orElse(null));
-			model.addAttribute("upcoming", edition.getModules().stream()
-					.flatMap(m -> m.getAssignments().stream())
-					.filter(a -> a.getDeadline() != null)
-					.filter(a -> a.getDeadline().isAfter(LocalDateTime.now()))
-					.max(Comparator.comparing(SubmitAssignmentSummaryDTO::getDeadline)).orElse(null));
+
+		if (authorizationService.canViewEditionManagerView(id)) {
+			SubmitEditionViewDTO edition = editionService.getDetailedEditionView(new EditionIdDTO().id(id));
+
+			model.addAttribute("edition", edition);
+			model.addAttribute("roles", editionService.getEditionRoles(id));
+			model.addAttribute("module", new ModuleCreateDTO().edition(new EditionIdDTO()));
+			model.addAttribute("grading", new EditionGradingPatchDTO());
+			model.addAttribute("exportOptions", new GradeExportOptions());
+			model.addAttribute("groups", edition.getModules().stream().map(SubmitModuleViewDTO::getId)
+					.collect(HashMap::new,
+							(map, moduleId) -> map.put(moduleId,
+									groupService.getGroupForPersonInModule(person.getId(), moduleId)
+											.orElse(null)),
+							HashMap::putAll));
+
+			return "edition/view";
+		} else if (authorizationService.canAssistEdition(id)) {
+			return "redirect:/edition/{id}/assist";
+		} else {
+			return "redirect:/edition/{id}/student";
+		}
+
+	}
+
+	/**
+	 * Gets the assist page for an edition. The main purpose of this page is for TAs to quickly find a group
+	 * and give feedback.
+	 *
+	 * @param  id The id of the edition
+	 * @return    The assist page
+	 */
+	@GetMapping("{id}/assist")
+	@PreAuthorize("@authorizationService.canAssistEdition(#id)")
+	public String getEditionAssistPage(@PathVariable Long id,
+			@RequestParam(name = "module", required = false) Long moduleId,
+			@RequestParam(name = "q", required = false) String query, Model model) {
+		SubmitEditionViewDTO edition = editionService.getDetailedEditionView(new EditionIdDTO().id(id));
+		SubmitModuleViewDTO module = moduleId == null
+				? edition.getModules().isEmpty() ? null
+						: moduleService.getDetailedModuleView(edition.getModules().get(0).getId())
+				: moduleService.getDetailedModuleView(moduleId);
+
+		model.addAttribute("edition", edition);
+		model.addAttribute("module", module);
+		if (module != null) {
+			List<StudentGroupDetailsDTO> allGroups = groupService
+					.getGroupDetails(groupService.getAllGroupsInModule(module.getId()).stream()
+							.map(StudentGroupSummaryDTO::getId).toList());
+			model.addAttribute("groups", groupService.sortAndFilterGroups(allGroups, query));
 		}
 
+		return "edition/assist";
+	}
+
+	/**
+	 * Gets the student page for an edition. This is the only edition page students typically see.
+	 *
+	 * @param  id The id of the edition
+	 * @return    The student page
+	 */
+	@GetMapping("{id}/student")
+	@PreAuthorize("@authorizationService.canViewEdition(#id)")
+	public String getEditionStudentPage(@AuthenticatedPerson Person person, @PathVariable Long id,
+			@RequestParam(name = "module", required = false) Long moduleId, Model model) {
+		SubmitEditionViewDTO edition = editionService.getDetailedEditionView(new EditionIdDTO().id(id));
+		SubmitModuleViewDTO module = moduleId == null
+				? edition.getModules().isEmpty() ? null
+						: moduleService.getDetailedModuleView(edition.getModules().get(0).getId())
+				: moduleService.getDetailedModuleView(moduleId);
+
 		model.addAttribute("edition", edition);
-		model.addAttribute("roles", editionService.getEditionRoles(id.getId()));
-		model.addAttribute("module", new ModuleCreateDTO().edition(new EditionIdDTO()));
-		model.addAttribute("grading", new EditionGradingPatchDTO());
-		model.addAttribute("exportOptions", new GradeExportOptions());
-		model.addAttribute("groups", edition.getModules().stream().map(SubmitModuleViewDTO::getId)
-				.collect(HashMap::new,
-						(map, moduleId) -> map.put(moduleId,
-								groupService.getGroupForPersonInModule(person.getId(), moduleId)
-										.orElse(null)),
-						HashMap::putAll));
-
-		return "edition/view";
+		model.addAttribute("module", module);
+		if (module != null) {
+			Optional<StudentGroupDetailsDTO> group = groupService.getGroupForPersonInModule(person.getId(),
+					module.getId());
+			Optional<SortedMap<SubmitAssignmentSummaryDTO, List<SubmitSubmissionViewDTO>>> submissionMap = group
+					.map(g -> submissionService.getSubmitSubmissionsPerAssignment(g.getId()));
+			model.addAttribute("group", group.orElse(null));
+			model.addAttribute("submissionMap",
+					submissionMap.orElse(null));
+			model.addAttribute("grades", submissionMap.stream().flatMap(map -> map.values().stream())
+					.flatMap(List::stream)
+					.flatMap(s -> s.getGrades().stream())
+					.collect(Collectors.toMap(GradeSummaryDTO::getId, Function.identity())));
+			model.addAttribute("moduleGrade",
+					gradeService.getModuleScore(new PersonIdDTO().id(person.getId()),
+							new ModuleIdDTO().id(module.getId())).orElse(null));
+			if (group.isEmpty()) {
+				model.addAttribute("groups", groupService.getAllSubmitGroupsInModule(module.getId(), null));
+			}
+		}
+		record AssignmentWithModule(SubmitModuleViewDTO module, SubmitAssignmentSummaryDTO assignment) {
+		}
+		model.addAttribute("upcomingDeadlines", edition.getModules().stream()
+				.flatMap(m -> m.getAssignments().stream().map(a -> new AssignmentWithModule(m, a)))
+				.filter(a -> a.assignment().getDeadline() != null
+						&& a.assignment().getDeadline().isAfter(LocalDateTime.now()))
+				.sorted(Comparator.comparing(a -> a.assignment().getDeadline()))
+				.limit(3)
+				.toList());
+		model.addAttribute("editionGrade", gradeService
+				.getEditionScore(new PersonIdDTO().id(person.getId()), new EditionIdDTO().id(person.getId()))
+				.orElse(null));
+
+		return "edition/student";
 	}
 
 	/**
@@ -230,7 +304,7 @@ public class EditionController {
 				.map(m -> moduleService.getModuleById(m.getId())).collect(Collectors.toList());
 		List<EditionTableRowDTO> data = statisticsService.getEditionTableData(edition, modules, q, null);
 
-		model.addAttribute("edition", edition);
+		model.addAttribute("edition", editionService.getDetailedEditionView(new EditionIdDTO().id(id)));
 		model.addAttribute("data", data);
 		model.addAttribute("progress", statisticsService.getEditionProgress(edition, data));
 
@@ -241,7 +315,7 @@ public class EditionController {
 	@PreAuthorize("@authorizationService.canViewEditionStudents(#id)")
 	public String getEditionStudentActions(@PathVariable Long id, @RequestParam(required = false) String q,
 			Model model) {
-		EditionDetailsDTO edition = editionService.getEdition(id);
+		SubmitEditionViewDTO edition = editionService.getDetailedEditionView(new EditionIdDTO().id(id));
 		List<MemberViewDTO> roles = roleService.getStudentRolesOfEdition(id, true);
 		if (q != null) {
 			String filter = q.toLowerCase();
@@ -274,7 +348,7 @@ public class EditionController {
 			@PathVariable Long id, Model model) {
 		boolean staff = authorizationService.isAtLeastTAInEdition(id);
 
-		EditionDetailsDTO edition = editionService.getEdition(id);
+		SubmitEditionViewDTO edition = editionService.getDetailedEditionView(new EditionIdDTO().id(id));
 		List<MemberViewDTO> roles = roleService.getStaffRolesOfEdition(id, staff);
 		if (q != null) {
 			String filter = q.toLowerCase();
@@ -305,7 +379,7 @@ public class EditionController {
 	@GetMapping("/{id}/statistics")
 	@PreAuthorize("@authorizationService.canViewEditionStatistics(#id)")
 	public String getStatistics(@PathVariable Long id, Model model) {
-		EditionDetailsDTO edition = editionService.getEdition(id);
+		SubmitEditionViewDTO edition = editionService.getDetailedEditionView(new EditionIdDTO().id(id));
 
 		model.addAttribute("edition", edition);
 		model.addAttribute("statistics", statisticsService.getEditionStatistics(id));
@@ -313,29 +387,6 @@ public class EditionController {
 		return "edition/statistics";
 	}
 
-	/**
-	 * Gets the statistics table page for an edition.
-	 *
-	 * @param  id    The id of the edition
-	 * @param  model The model to add details to
-	 * @return       The page to be loaded
-	 */
-	@GetMapping("/{id}/statistics-table")
-	@PreAuthorize("@authorizationService.canViewEditionStatistics(#id)")
-	public String getStatisticsTable(@PathVariable Long id, @RequestParam(required = false) String q,
-			@RequestParam(required = false) String status, Model model) {
-		EditionDetailsDTO edition = editionService.getEdition(id);
-		List<ModuleDetailsDTO> modules = edition.getModules().stream()
-				.map(m -> moduleService.getModuleById(m.getId())).collect(Collectors.toList());
-
-		model.addAttribute("edition", edition);
-		model.addAttribute("modules", modules);
-		model.addAttribute("data", statisticsService.getEditionTableData(edition, modules, q,
-				status == null || status.equals("all") ? null : GradeStatistics.valueOf(status)));
-
-		return "edition/statistics_table";
-	}
-
 	@PostMapping("/{id}/archive")
 	@PreAuthorize("@authorizationService.canArchiveEdition(#id)")
 	public String archiveEdition(@PathVariable Long id) {
@@ -385,17 +436,8 @@ public class EditionController {
 	@Validated
 	@PostMapping
 	@PreAuthorize("@authorizationService.canCreateEdition(#edition.getCourse().getId())")
-	public String addEdition(@AuthenticatedPerson Person person,
-			@Valid @ModelAttribute("create") SubmitEditionCreateDTO edition) {
+	public String addEdition(@Valid @ModelAttribute("create") SubmitEditionCreateDTO edition) {
 		Long id = editionService.addEdition(mapper.map(edition, EditionCreateDTO.class));
-		if (edition.getImportFromCohort()) {
-			editionService.addStudentsFromCohort(id, edition.getCohort());
-		}
-
-		// TODO remove when this happens in LC
-		editionService.addUser(id, person);
-		roleService.patchRole(person.getId(), id, new RolePatchDTO().type(RolePatchDTO.TypeEnum.TEACHER));
-
 		return "redirect:/edition/" + id;
 	}
 
@@ -528,7 +570,7 @@ public class EditionController {
 	@GetMapping("/{id}/announcements")
 	@PreAuthorize("@authorizationService.canEditAnnouncementsForEdition(#id)")
 	public String getEditionAnnouncements(@PathVariable Long id, Model model) {
-		model.addAttribute("edition", editionService.getEdition(id));
+		model.addAttribute("edition", editionService.getDetailedEditionView(new EditionIdDTO().id(id)));
 		model.addAttribute("announcements", announcementService.getEditionAnnouncements(id));
 		model.addAttribute("announcement", new EditionAnnouncementCreateDTO());
 
diff --git a/src/main/java/nl/tudelft/submit/controller/GradeController.java b/src/main/java/nl/tudelft/submit/controller/GradeController.java
index 148c98d299add7b35a7785f1e9b04572fc5ff00b..92e2ca899b8a18f9db88d1207e864de4e3c60087 100644
--- a/src/main/java/nl/tudelft/submit/controller/GradeController.java
+++ b/src/main/java/nl/tudelft/submit/controller/GradeController.java
@@ -35,7 +35,6 @@ import nl.tudelft.submit.dto.view.TestGradeViewDTO;
 import nl.tudelft.submit.model.grading.CalculatedScore;
 import nl.tudelft.submit.model.grading.GradingFormula;
 import nl.tudelft.submit.service.AssignmentService;
-import nl.tudelft.submit.service.FeedbackService;
 import nl.tudelft.submit.service.GradeService;
 
 import org.springframework.beans.factory.annotation.Autowired;
@@ -51,8 +50,6 @@ public class GradeController {
 	@Autowired
 	private GradeService gradeService;
 	@Autowired
-	private FeedbackService feedbackService;
-	@Autowired
 	private AssignmentService assignmentService;
 
 	@PostMapping("/group/{groupId}/{assignmentId}")
@@ -66,6 +63,17 @@ public class GradeController {
 				+ create.getSubmissionId();
 	}
 
+	@PostMapping("/edition/{editionId}/{assignmentId}")
+	@PreAuthorize("@authorizationService.canGradeSubmission(#create.submissionId)")
+	public String addGradeInEdition(@AuthenticatedPerson Person person, @PathVariable Long editionId,
+			@PathVariable Long assignmentId,
+			@ModelAttribute("feedbackCreate") GradedFeedbackCreateDTO create) {
+		gradeService.addGradedFeedback(person.getId(), assignmentId, create);
+
+		return "redirect:/edition/{editionId}/assist?module="
+				+ assignmentService.getAssignmentDetails(assignmentId).getModule().getId();
+	}
+
 	@PostMapping("/group/remove-highest/{groupId}/{submissionId}")
 	@PreAuthorize("@authorizationService.canRemoveSubmissionGrade(#submissionId)")
 	public String removeHighestGradeForGroup(@PathVariable Long groupId, @PathVariable Long submissionId) {
diff --git a/src/main/java/nl/tudelft/submit/controller/ModuleController.java b/src/main/java/nl/tudelft/submit/controller/ModuleController.java
index 51b1026117c81d3cb6b3e612a1856e758ab49b25..86568b1988bbb8399a36797e516c600e14f6a084 100644
--- a/src/main/java/nl/tudelft/submit/controller/ModuleController.java
+++ b/src/main/java/nl/tudelft/submit/controller/ModuleController.java
@@ -98,8 +98,11 @@ public class ModuleController {
 	 * @return       the page to load
 	 */
 	@GetMapping("/{id}")
-	@PreAuthorize("@authorizationService.canViewModule(#id)")
+	@PreAuthorize("@authorizationService.canViewModule(#id) or @authorizationService.canViewModuleGroups(#id)")
 	public String getModule(@PathVariable Long id, Model model) {
+		if (!authorizationService.canViewModule(id))
+			return "redirect:/module/{id}/groups";
+
 		SubmitModuleViewDTO module = moduleService.getDetailedModuleView(id);
 
 		model.addAttribute("edition", editionService.getEdition(module.getEdition().getId()));
@@ -151,7 +154,7 @@ public class ModuleController {
 					.collect(Collectors.toList());
 		}
 
-		ModuleDetailsDTO module = moduleService.getModuleById(id);
+		SubmitModuleViewDTO module = moduleService.getDetailedModuleView(id);
 
 		model.addAttribute("edition", editionService.getEdition(module.getEdition().getId()));
 		model.addAttribute("module", module);
@@ -174,7 +177,7 @@ public class ModuleController {
 	@GetMapping("/{id}/groups/create")
 	@PreAuthorize("@authorizationService.canCreateGroup(#id)")
 	public String getModuleGroups(@AuthenticatedPerson Person person, @PathVariable Long id, Model model) {
-		ModuleDetailsDTO module = moduleService.getModuleById(id);
+		SubmitModuleViewDTO module = moduleService.getDetailedModuleView(id);
 
 		model.addAttribute("edition", editionService.getEdition(module.getEdition().getId()));
 		model.addAttribute("module", module);
@@ -197,7 +200,7 @@ public class ModuleController {
 		List<ModuleTableRowDTO> data = statisticsService.getModuleTableData(module, q, null);
 
 		model.addAttribute("edition", editionService.getEdition(module.getEdition().getId()));
-		model.addAttribute("module", module);
+		model.addAttribute("module", moduleService.getDetailedModuleView(id));
 		model.addAttribute("data", data);
 		model.addAttribute("progress", statisticsService.getModuleProgress(module, data));
 
@@ -217,7 +220,7 @@ public class ModuleController {
 		ModuleDetailsDTO module = moduleService.getModuleById(id);
 
 		model.addAttribute("edition", editionService.getEdition(module.getEdition().getId()));
-		model.addAttribute("module", module);
+		model.addAttribute("module", moduleService.getDetailedModuleView(id));
 		model.addAttribute("statistics", statisticsService.getModuleStatistics(module));
 
 		return "module/statistics";
diff --git a/src/main/java/nl/tudelft/submit/controller/StudentGroupController.java b/src/main/java/nl/tudelft/submit/controller/StudentGroupController.java
index 69ef8cf5d54c9da7f44e0b3c2912504e6c81842c..d085a4c32b909d1bcae9c5a87047f89beaa529dd 100644
--- a/src/main/java/nl/tudelft/submit/controller/StudentGroupController.java
+++ b/src/main/java/nl/tudelft/submit/controller/StudentGroupController.java
@@ -153,6 +153,29 @@ public class StudentGroupController {
 		return "group/view";
 	}
 
+	/**
+	 * Gets a fragment with the list of submissions for a group. This is used in the edition assist page.
+	 *
+	 * @param  id The id of the group
+	 * @return    The fragment with submissions
+	 */
+	@GetMapping("{id}/submissions")
+	@PreAuthorize("@authorizationService.canViewGroup(#id)")
+	public String getGroupSubmissions(@PathVariable Long id, Model model) {
+		SubmitGroupDetailsDTO group = groupService.getSubmitGroupDetails(id);
+		ModuleDetailsDTO module = moduleService.getModuleById(groupService.getModuleIdFromGroup(id));
+
+		model.addAttribute("module", module);
+		model.addAttribute("edition", editionService.getEdition(module.getEdition().getId()));
+		model.addAttribute("group", group);
+		model.addAttribute("grades", group.getSubmissionMap().values().stream()
+				.flatMap(Collection::stream)
+				.flatMap(s -> s.getGrades().stream())
+				.collect(Collectors.toMap(GradeSummaryDTO::getId, Function.identity())));
+
+		return "edition/assist :: submissions";
+	}
+
 	@PostMapping
 	@PreAuthorize("@authorizationService.canCreateGroup(#create.getModule().getId())")
 	public String createGroup(@AuthenticatedPerson Person person,
@@ -206,7 +229,9 @@ public class StudentGroupController {
 	@PreAuthorize("@authorizationService.canJoinGroup(#id)")
 	public String joinGroup(@AuthenticatedPerson Person person, @PathVariable Long id) {
 		groupService.addPeopleToGroup(id, List.of(person.getUsername()));
-		return "redirect:/group/" + id;
+
+		ModuleDetailsDTO module = moduleService.getModuleById(groupService.getModuleIdFromGroup(id));
+		return "redirect:/edition/" + module.getEdition().getId() + "/student?module=" + module.getId();
 	}
 
 	/**
diff --git a/src/main/java/nl/tudelft/submit/controller/SubmissionController.java b/src/main/java/nl/tudelft/submit/controller/SubmissionController.java
index ec782b85fd6794d952fc9c624eefbd63be79b40f..f133221f6208f0c74258ae883e7e826ddd754af5 100644
--- a/src/main/java/nl/tudelft/submit/controller/SubmissionController.java
+++ b/src/main/java/nl/tudelft/submit/controller/SubmissionController.java
@@ -107,9 +107,10 @@ public class SubmissionController {
 
 		createSubmissionDto.setSubmitter(new PersonIdDTO().id(person.getId()));
 
+		AssignmentDetailsDTO assignment = assignmentService
+				.getAssignmentDetails(createSubmissionDto.getAssignment().getId());
+
 		try {
-			AssignmentDetailsDTO assignment = assignmentService
-					.getAssignmentDetails(createSubmissionDto.getAssignment().getId());
 			StudentGroupDetailsDTO group = groupService
 					.getGroupForPersonInModule(person.getId(), assignment.getModule().getId()).get();
 
@@ -140,8 +141,15 @@ public class SubmissionController {
 			e.printStackTrace(); // The email will not be sent, TODO a different email can be sent
 		}
 
-		return page.equals("group") ? "redirect:/group/" + createSubmissionDto.getGroup().getId()
-				: "redirect:/assignment/" + createSubmissionDto.getAssignment().getId();
+		return switch (page) {
+			case "group" -> "redirect:/group/" + createSubmissionDto.getGroup().getId();
+			case "student" -> {
+				ModuleDetailsDTO module = moduleService.getModuleById(assignment.getModule().getId());
+				yield "redirect:/edition/" + module.getEdition().getId() + "/student?module="
+						+ module.getId();
+			}
+			default -> "redirect:/assignment/" + createSubmissionDto.getAssignment().getId();
+		};
 	}
 
 	/**
diff --git a/src/main/java/nl/tudelft/submit/dto/create/SubmitEditionCreateDTO.java b/src/main/java/nl/tudelft/submit/dto/create/SubmitEditionCreateDTO.java
index d1d5f8656e4d6e2cc8b5a8ebb622ff566fdbff19..9c99522739c550fec9710e7336bea3b14c8881b4 100644
--- a/src/main/java/nl/tudelft/submit/dto/create/SubmitEditionCreateDTO.java
+++ b/src/main/java/nl/tudelft/submit/dto/create/SubmitEditionCreateDTO.java
@@ -28,7 +28,6 @@ import lombok.Data;
 import lombok.NoArgsConstructor;
 import nl.tudelft.labracore.api.dto.CohortIdDTO;
 import nl.tudelft.labracore.api.dto.CourseIdDTO;
-import nl.tudelft.labracore.api.dto.EditionCreateDTO;
 
 import org.springframework.format.annotation.DateTimeFormat;
 
@@ -47,9 +46,6 @@ public class SubmitEditionCreateDTO {
 	@NotNull
 	private CohortIdDTO cohort;
 
-	@NotNull
-	private EditionCreateDTO.EnrollabilityEnum enrollability;
-
 	@NotNull
 	@DateTimeFormat(pattern = "dd-MM-yyyy HH:mm")
 	private LocalDateTime startDate;
@@ -58,8 +54,4 @@ public class SubmitEditionCreateDTO {
 	@DateTimeFormat(pattern = "dd-MM-yyyy HH:mm")
 	private LocalDateTime endDate;
 
-	@Builder.Default
-	@NotNull
-	private Boolean importFromCohort = false;
-
 }
diff --git a/src/main/java/nl/tudelft/submit/dto/patch/SubmitEditionPatchDTO.java b/src/main/java/nl/tudelft/submit/dto/patch/SubmitEditionPatchDTO.java
index fbf1f62e1a6c6f666576686e520c82daa29861e6..5c7e3a2e23b9aec1f56b983c25d4fea2f55a23ab 100644
--- a/src/main/java/nl/tudelft/submit/dto/patch/SubmitEditionPatchDTO.java
+++ b/src/main/java/nl/tudelft/submit/dto/patch/SubmitEditionPatchDTO.java
@@ -18,7 +18,6 @@
 package nl.tudelft.submit.dto.patch;
 
 import lombok.*;
-import nl.tudelft.labracore.api.dto.EditionPatchDTO;
 import nl.tudelft.librador.dto.patch.Patch;
 import nl.tudelft.submit.model.SubmitEdition;
 
@@ -30,7 +29,6 @@ import nl.tudelft.submit.model.SubmitEdition;
 public class SubmitEditionPatchDTO extends Patch<SubmitEdition> {
 
 	private String name;
-	private EditionPatchDTO.EnrollabilityEnum enrollability;
 	private String contactPolicy;
 	private String email;
 
diff --git a/src/main/java/nl/tudelft/submit/dto/view/labracore/SubmitModuleViewDTO.java b/src/main/java/nl/tudelft/submit/dto/view/labracore/SubmitModuleViewDTO.java
index ae4fae7cd041406070cf69881919a48d0895d657..5aa666ab3a0421c30dcac8aaea3dfeb5bf800931 100644
--- a/src/main/java/nl/tudelft/submit/dto/view/labracore/SubmitModuleViewDTO.java
+++ b/src/main/java/nl/tudelft/submit/dto/view/labracore/SubmitModuleViewDTO.java
@@ -22,6 +22,7 @@ import java.util.List;
 import javax.validation.constraints.NotNull;
 
 import lombok.AllArgsConstructor;
+import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 import nl.tudelft.labracore.api.dto.EditionSummaryDTO;
@@ -29,6 +30,7 @@ import nl.tudelft.labracore.api.dto.ModuleDivisionSummaryDTO;
 import nl.tudelft.submit.dto.view.GradingFormulaViewDTO;
 
 @Data
+@Builder
 @NoArgsConstructor
 @AllArgsConstructor
 public class SubmitModuleViewDTO {
diff --git a/src/main/java/nl/tudelft/submit/model/Feedback.java b/src/main/java/nl/tudelft/submit/model/Feedback.java
index 7dc8efd8505d3301881a722c16c6ca8b7640e778..1db5bdb7216b626548bb6d87127d859a839e38ae 100644
--- a/src/main/java/nl/tudelft/submit/model/Feedback.java
+++ b/src/main/java/nl/tudelft/submit/model/Feedback.java
@@ -47,6 +47,7 @@ public class Feedback {
 
 	@Lob
 	@NotEmpty
+	@Column(columnDefinition = "TEXT")
 	private String textualFeedback;
 
 	private String fileFeedback;
diff --git a/src/main/java/nl/tudelft/submit/model/announcement/GlobalAnnouncement.java b/src/main/java/nl/tudelft/submit/model/announcement/GlobalAnnouncement.java
index aaddf4b74e9f2960d8affa7994860b022e42efcb..ef3de36794e7b395e15a12a400a657d79a63fe97 100644
--- a/src/main/java/nl/tudelft/submit/model/announcement/GlobalAnnouncement.java
+++ b/src/main/java/nl/tudelft/submit/model/announcement/GlobalAnnouncement.java
@@ -22,6 +22,7 @@ import javax.persistence.Entity;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.NoArgsConstructor;
+import lombok.ToString;
 import lombok.experimental.SuperBuilder;
 
 @Data
@@ -29,6 +30,7 @@ import lombok.experimental.SuperBuilder;
 @SuperBuilder
 @NoArgsConstructor
 @EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
 public class GlobalAnnouncement extends Announcement {
 
 }
diff --git a/src/main/java/nl/tudelft/submit/model/api/EditionApiKey.java b/src/main/java/nl/tudelft/submit/model/api/EditionApiKey.java
index c1c1db0d7d4d098f2e693f375549a9dfc6fd6cd7..997b4245340664676fbdc66a2400fda2c75e6c6f 100644
--- a/src/main/java/nl/tudelft/submit/model/api/EditionApiKey.java
+++ b/src/main/java/nl/tudelft/submit/model/api/EditionApiKey.java
@@ -17,10 +17,7 @@
  */
 package nl.tudelft.submit.model.api;
 
-import javax.persistence.Entity;
-import javax.persistence.GeneratedValue;
-import javax.persistence.GenerationType;
-import javax.persistence.Id;
+import javax.persistence.*;
 import javax.validation.constraints.NotBlank;
 import javax.validation.constraints.NotNull;
 import javax.validation.constraints.Size;
@@ -46,6 +43,7 @@ public class EditionApiKey {
 
 	@NotBlank
 	@Size(max = 127)
+	@Column(name = "api_key")
 	private String key;
 
 }
diff --git a/src/main/java/nl/tudelft/submit/model/script/SubmissionKey.java b/src/main/java/nl/tudelft/submit/model/script/SubmissionKey.java
index 2e5e86e78a0da767a2d8b32adbc337bc01c40d5f..8bd44b7d05814174a1cd2be59aa3bdebc70f8ce6 100644
--- a/src/main/java/nl/tudelft/submit/model/script/SubmissionKey.java
+++ b/src/main/java/nl/tudelft/submit/model/script/SubmissionKey.java
@@ -45,6 +45,7 @@ public class SubmissionKey {
 	private Long scriptId;
 
 	@NotNull
+	@Column(name = "api_key")
 	private String key;
 
 	@NotNull
diff --git a/src/main/java/nl/tudelft/submit/repository/script/ScriptResultRepository.java b/src/main/java/nl/tudelft/submit/repository/script/ScriptResultRepository.java
index 97c31a7c2e0132802cd5d75ca75f5953c80a4069..a677fde836832ae9d5d728a5446be2172e22b878 100644
--- a/src/main/java/nl/tudelft/submit/repository/script/ScriptResultRepository.java
+++ b/src/main/java/nl/tudelft/submit/repository/script/ScriptResultRepository.java
@@ -25,4 +25,7 @@ public interface ScriptResultRepository extends AbstractScriptResultRepository<S
 	ScriptResult findByScriptIdAndSubmissionId(Long scriptId, Long submissionId);
 
 	List<ScriptResult> findAllByScriptWagonIdAndSubmissionId(Long wagonId, Long submissionId);
+
+	void deleteAllByScriptId(Long scriptId);
+
 }
diff --git a/src/main/java/nl/tudelft/submit/security/AuthorizationService.java b/src/main/java/nl/tudelft/submit/security/AuthorizationService.java
index a265ccbd5235ac32d7254aefb06ac1c468292784..96ce3b9d1b7c673017eaca25cd02a7209549c560 100644
--- a/src/main/java/nl/tudelft/submit/security/AuthorizationService.java
+++ b/src/main/java/nl/tudelft/submit/security/AuthorizationService.java
@@ -97,7 +97,7 @@ public class AuthorizationService {
 	 *
 	 * @return the authenticated person object
 	 */
-	private Person getAuthPerson() {
+	public Person getAuthPerson() {
 		Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
 		return principal instanceof LabradorUserDetails details ? details.getUser() : null;
 	}
@@ -281,6 +281,27 @@ public class AuthorizationService {
 		return isAtLeastStudentInEdition(editionId) || isTeacherROInEdition(editionId);
 	}
 
+	/**
+	 * Checks whether the authenticated user can view the assistant view of an edition.
+	 *
+	 * @param  editionId The id of the edition
+	 * @return           True iff the user can view the edition assitant view
+	 */
+	public boolean canAssistEdition(Long editionId) {
+		return isAtLeastTAInEdition(editionId);
+	}
+
+	/**
+	 * Checks whether the authenticated user can view the manager view of an edition. If false, the user can
+	 * only see the assist or student view.
+	 *
+	 * @param  editionId The id of the edition
+	 * @return           True iff the user can view the edition manager view
+	 */
+	public boolean canViewEditionManagerView(Long editionId) {
+		return isAtLeastHeadTAInEdition(editionId);
+	}
+
 	/**
 	 * Checks whether the authenticated user can view of an edition's students.
 	 *
diff --git a/src/main/java/nl/tudelft/submit/service/AnnouncementService.java b/src/main/java/nl/tudelft/submit/service/AnnouncementService.java
index c233bb66f77aa8650820900c9ab908b4e818dfd0..04b1cb432a7dc647fc8e91497c79688eba42610f 100644
--- a/src/main/java/nl/tudelft/submit/service/AnnouncementService.java
+++ b/src/main/java/nl/tudelft/submit/service/AnnouncementService.java
@@ -22,7 +22,6 @@ import java.util.List;
 
 import javax.transaction.Transactional;
 
-import nl.tudelft.librador.SpringContext;
 import nl.tudelft.submit.dto.create.announcement.AnnouncementCreateDTO;
 import nl.tudelft.submit.model.announcement.Announcement;
 import nl.tudelft.submit.model.announcement.EditionAnnouncement;
@@ -32,6 +31,7 @@ import nl.tudelft.submit.repository.announcement.EditionAnnouncementRepository;
 import nl.tudelft.submit.repository.announcement.GlobalAnnouncementRepository;
 
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
 import org.springframework.stereotype.Service;
 
 @Service
@@ -43,6 +43,9 @@ public class AnnouncementService {
 	@Autowired
 	private EditionAnnouncementRepository editionAnnouncementRepository;
 
+	@Autowired
+	private ApplicationContext springContext;
+
 	/**
 	 * Gets all global announcements.
 	 *
@@ -105,7 +108,7 @@ public class AnnouncementService {
 	 * @return              The id of the new announcement
 	 */
 	public <A extends Announcement> Long addAnnouncement(AnnouncementCreateDTO<A> announcement) {
-		AnnouncementRepository<A> repository = SpringContext.getBean(announcement.repositoryClass());
+		AnnouncementRepository<A> repository = springContext.getBean(announcement.repositoryClass());
 		return repository.save(announcement.apply()).getId();
 	}
 
diff --git a/src/main/java/nl/tudelft/submit/service/AssignmentService.java b/src/main/java/nl/tudelft/submit/service/AssignmentService.java
index c245b1f7dd05e5fdfe39b9067c5cb911b1321e2a..081c2c6e70a7ec6abbb006307419d64209e1bbee 100644
--- a/src/main/java/nl/tudelft/submit/service/AssignmentService.java
+++ b/src/main/java/nl/tudelft/submit/service/AssignmentService.java
@@ -71,6 +71,7 @@ public class AssignmentService {
 	private AssignmentNoteRepository noteRepository;
 
 	@Autowired
+	@Lazy
 	private StudentGroupService studentGroupService;
 	@Autowired
 	private SubmissionService submissionService;
@@ -84,6 +85,7 @@ public class AssignmentService {
 	@Autowired
 	private FeedbackService feedbackService;
 	@Autowired
+	@Lazy
 	private GradeService gradeService;
 
 	/**
diff --git a/src/main/java/nl/tudelft/submit/service/EditionService.java b/src/main/java/nl/tudelft/submit/service/EditionService.java
index ff34bcdadcdfc8eb4f5a253793b514115fd424ab..cb96d6bb78e618cac367c13ce629af85c7259d09 100644
--- a/src/main/java/nl/tudelft/submit/service/EditionService.java
+++ b/src/main/java/nl/tudelft/submit/service/EditionService.java
@@ -44,6 +44,7 @@ import nl.tudelft.submit.repository.SubmitEditionRepository;
 
 import org.modelmapper.ModelMapper;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -71,6 +72,7 @@ public class EditionService {
 	@Autowired
 	private PersonService personService;
 	@Autowired
+	@Lazy
 	private GradeService gradeService;
 	@Autowired
 	private FormulaService formulaService;
@@ -332,6 +334,7 @@ public class EditionService {
 	 */
 	@Transactional
 	public Long addEdition(EditionCreateDTO edition) {
+		edition.setEnrollability(EditionCreateDTO.EnrollabilityEnum.OPEN);
 		Long editionId = editionApi.addEdition(edition).block();
 		SubmitEdition submitEdition = getOrCreateSubmitEdition(editionId);
 		submitEdition.setHidden(false);
diff --git a/src/main/java/nl/tudelft/submit/service/FormulaService.java b/src/main/java/nl/tudelft/submit/service/FormulaService.java
index 172219e8d261e340577bc266050729d16b97c524..4b73d82c98982200a7a31c80c80a98d61f2979f3 100644
--- a/src/main/java/nl/tudelft/submit/service/FormulaService.java
+++ b/src/main/java/nl/tudelft/submit/service/FormulaService.java
@@ -32,7 +32,6 @@ import java.util.regex.Matcher;
 
 import javax.transaction.Transactional;
 
-import nl.tudelft.librador.SpringContext;
 import nl.tudelft.submit.dto.create.grading.AbstractGradingFormulaCreateDTO;
 import nl.tudelft.submit.dto.create.grading.GradingFormulaCreateDTO;
 import nl.tudelft.submit.dto.helper.Expression;
@@ -47,6 +46,8 @@ import nl.tudelft.submit.repository.grading.GradingFormulaRepository;
 
 import org.modelmapper.ModelMapper;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.data.util.Pair;
 import org.springframework.stereotype.Service;
 import org.typemeta.funcj.data.IList;
@@ -73,8 +74,12 @@ public class FormulaService {
 	}
 
 	@Autowired
+	@Lazy
 	private GradeService gradeService;
 
+	@Autowired
+	private ApplicationContext springContext;
+
 	private Parser<Token, Expression> parser;
 
 	private final ModelMapper mapper = new ModelMapper();
@@ -139,7 +144,7 @@ public class FormulaService {
 			GradingFormulaCreateDTO create) {
 		AbstractGradingFormulaCreateDTO<E, T> formula = create.convertToCreateDTO();
 
-		GradingFormulaRepository<E, T> repository = SpringContext.getBean(formula.repositoryClass());
+		GradingFormulaRepository<E, T> repository = springContext.getBean(formula.repositoryClass());
 		Optional<T> optional = repository.findById(formula.getEntityId());
 
 		E id;
diff --git a/src/main/java/nl/tudelft/submit/service/GradeService.java b/src/main/java/nl/tudelft/submit/service/GradeService.java
index 7e1dc56ac2ca839e987b7bb4dd5f12badbf3c19d..e2d5b6942fdd3dd9402fae1a09458fa584dcf5fe 100644
--- a/src/main/java/nl/tudelft/submit/service/GradeService.java
+++ b/src/main/java/nl/tudelft/submit/service/GradeService.java
@@ -56,6 +56,7 @@ import nl.tudelft.submit.repository.grading.ModuleGradingFormulaRepository;
 
 import org.modelmapper.ModelMapper;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 
 @Service
@@ -108,6 +109,7 @@ public class GradeService {
 	@Autowired
 	private FeedbackService feedbackService;
 	@Autowired
+	@Lazy
 	private EditionService editionService;
 	@Autowired
 	private AssignmentService assignmentService;
diff --git a/src/main/java/nl/tudelft/submit/service/ModuleDivisionService.java b/src/main/java/nl/tudelft/submit/service/ModuleDivisionService.java
index e7a3535baab15d3a5962d4403ac27c534e9fd15a..3817fcb2b4fc759f759bd0cc51f1dab153aed4d4 100644
--- a/src/main/java/nl/tudelft/submit/service/ModuleDivisionService.java
+++ b/src/main/java/nl/tudelft/submit/service/ModuleDivisionService.java
@@ -28,6 +28,7 @@ import nl.tudelft.submit.enums.DivisionsGenerateOrder;
 import nl.tudelft.submit.enums.DivisionsGenerateType;
 
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 
 @Service
@@ -37,9 +38,11 @@ public class ModuleDivisionService {
 	private ModuleDivisionControllerApi divisionApi;
 
 	@Autowired
+	@Lazy
 	private EditionService editionService;
 
 	@Autowired
+	@Lazy
 	private ModuleService moduleService;
 
 	/**
diff --git a/src/main/java/nl/tudelft/submit/service/ModuleService.java b/src/main/java/nl/tudelft/submit/service/ModuleService.java
index b8cfe71ca8c38be0823e203d8840bb1037c0f284..56866a4d93d8b37ad94bc9d97098f01714dd723d 100644
--- a/src/main/java/nl/tudelft/submit/service/ModuleService.java
+++ b/src/main/java/nl/tudelft/submit/service/ModuleService.java
@@ -33,6 +33,7 @@ import nl.tudelft.submit.dto.view.labracore.SubmitModuleViewDTO;
 
 import org.modelmapper.ModelMapper;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 
 @Service
@@ -49,6 +50,7 @@ public class ModuleService {
 	private ModuleControllerApi moduleApi;
 
 	@Autowired
+	@Lazy
 	private GradeService gradeService;
 	@Autowired
 	private AssignmentService assignmentService;
diff --git a/src/main/java/nl/tudelft/submit/service/NoteService.java b/src/main/java/nl/tudelft/submit/service/NoteService.java
index 9cb8dbfb4b1ee3214f15c375540d1b92f44ee288..37f5def41ac44770906f12f13d9f689e87181d25 100644
--- a/src/main/java/nl/tudelft/submit/service/NoteService.java
+++ b/src/main/java/nl/tudelft/submit/service/NoteService.java
@@ -21,28 +21,32 @@ import java.util.Optional;
 
 import javax.transaction.Transactional;
 
-import nl.tudelft.librador.SpringContext;
 import nl.tudelft.submit.dto.create.note.NoteCreateDTO;
 import nl.tudelft.submit.dto.patch.note.NotePatchDTO;
 import nl.tudelft.submit.model.note.Note;
 import nl.tudelft.submit.repository.note.NoteRepository;
 
 import org.modelmapper.ModelMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
 import org.springframework.stereotype.Service;
 
 @Service
 @Transactional
 public class NoteService {
 
+	@Autowired
+	private ApplicationContext springContext;
+
 	private final ModelMapper mapper = new ModelMapper();
 
 	public <ID, T extends Note<ID>, CREATE extends NoteCreateDTO<ID, T>> Long addOrUpdateNote(CREATE create) {
-		NoteRepository<ID, T> repository = SpringContext.getBean(create.repositoryClass());
+		NoteRepository<ID, T> repository = springContext.getBean(create.repositoryClass());
 		Optional<T> optional = repository.findByEntityId(create.getEntityId());
 
 		if (optional.isPresent()) {
 			T present = optional.get();
-			return ((NotePatchDTO<ID, T>) mapper.map(create, optional.get().patchClass()))
+			return (mapper.<NotePatchDTO<ID, T>>map(create, optional.get().patchClass()))
 					.apply(present).getId();
 		}
 
diff --git a/src/main/java/nl/tudelft/submit/service/PersonService.java b/src/main/java/nl/tudelft/submit/service/PersonService.java
index 374121f8d61960e2c6fd103b91186bf21150b245..7189e03782536d6ea5b75967946511aa6caf4dc8 100644
--- a/src/main/java/nl/tudelft/submit/service/PersonService.java
+++ b/src/main/java/nl/tudelft/submit/service/PersonService.java
@@ -24,6 +24,7 @@ import nl.tudelft.labracore.api.PersonControllerApi;
 import nl.tudelft.labracore.api.dto.*;
 
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 
 @Service
@@ -35,6 +36,7 @@ public class PersonService {
 	@Autowired
 	private AssignmentService assignmentService;
 	@Autowired
+	@Lazy
 	private EditionService editionService;
 
 	/**
diff --git a/src/main/java/nl/tudelft/submit/service/ScriptService.java b/src/main/java/nl/tudelft/submit/service/ScriptService.java
index 92daca9ea15064d62208620b65cf7f7c6fb6a614..62c00d1277792ef02efcc47984a31bfdddef5a53 100644
--- a/src/main/java/nl/tudelft/submit/service/ScriptService.java
+++ b/src/main/java/nl/tudelft/submit/service/ScriptService.java
@@ -49,11 +49,11 @@ import nl.tudelft.submit.model.script.*;
 import nl.tudelft.submit.repository.SubmissionKeyRepository;
 import nl.tudelft.submit.repository.script.*;
 
-import org.modelmapper.ModelMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.core.io.FileSystemResource;
 import org.springframework.core.io.Resource;
 import org.springframework.http.*;
@@ -132,16 +132,11 @@ public class ScriptService {
 	@Autowired
 	private VersionService versionService;
 
-	@Autowired
-	private FeedbackService feedbackService;
-
 	@Autowired
 	private FileService fileService;
 
 	@Autowired
-	private StudentGroupService groupService;
-
-	@Autowired
+	@Lazy
 	private GradeService gradeService;
 
 	@Autowired
@@ -150,12 +145,6 @@ public class ScriptService {
 	@Autowired
 	private NotificationService notificationService;
 
-	@Autowired
-	private EmailService emailService;
-
-	@Autowired
-	private ModelMapper modelMapper;
-
 	private RestTemplate restTemplate = new RestTemplate();
 
 	private ObjectMapper objectMapper = new ObjectMapper();
@@ -525,6 +514,7 @@ public class ScriptService {
 	 * Removes a script from a scriptWagon.
 	 */
 	public void removeScriptById(Long id) {
+		scriptResultRepository.deleteAllByScriptId(id);
 		scriptRepository.deleteById(id);
 	}
 
diff --git a/src/main/java/nl/tudelft/submit/service/StatisticsService.java b/src/main/java/nl/tudelft/submit/service/StatisticsService.java
index eaeed64360d97e9ba774ab36dde5087fa34496c0..7c24e36afa95551160550dfd55e9a2a7276572fa 100644
--- a/src/main/java/nl/tudelft/submit/service/StatisticsService.java
+++ b/src/main/java/nl/tudelft/submit/service/StatisticsService.java
@@ -36,6 +36,7 @@ import nl.tudelft.submit.model.grading.*;
 
 import org.modelmapper.ModelMapper;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 
 @Service
@@ -43,6 +44,7 @@ import org.springframework.stereotype.Service;
 public class StatisticsService {
 
 	@Autowired
+	@Lazy
 	private EditionService editionService;
 	@Autowired
 	private PersonService personService;
diff --git a/src/main/java/nl/tudelft/submit/service/StudentGroupService.java b/src/main/java/nl/tudelft/submit/service/StudentGroupService.java
index d122d986e60e16bb2abea4ff68f94023ec6e99f3..22ce9407afd66ed790ceb7aa5b1627971f56e67c 100644
--- a/src/main/java/nl/tudelft/submit/service/StudentGroupService.java
+++ b/src/main/java/nl/tudelft/submit/service/StudentGroupService.java
@@ -100,6 +100,10 @@ public class StudentGroupService {
 		return groupCache.getOrThrow(id);
 	}
 
+	public List<StudentGroupDetailsDTO> getGroupDetails(List<Long> ids) {
+		return groupCache.get(ids);
+	}
+
 	/**
 	 * Returns a list with overviews of all the groups in a module including the instructor note if the
 	 * requester is staff.
@@ -382,4 +386,42 @@ public class StudentGroupService {
 
 		return map;
 	}
+
+	/**
+	 * Filters a list of groups based on a search query. Search is based on group name, netID, student number,
+	 * and student name.
+	 *
+	 * @param  groups The list of groups
+	 * @param  query  The query
+	 * @return        The filtered list of groups
+	 */
+	public List<StudentGroupDetailsDTO> sortAndFilterGroups(List<StudentGroupDetailsDTO> groups,
+			String query) {
+		// Sort by name or number
+		Pattern number = Pattern.compile("\\d+");
+		if (groups.stream().allMatch(g -> number.matcher(g.getName()).find())) {
+			groups.sort((g1, g2) -> {
+				Matcher m1 = number.matcher(g1.getName());
+				Matcher m2 = number.matcher(g2.getName());
+				m1.find();
+				m2.find();
+				return Integer.compare(Integer.parseInt(m1.group()), Integer.parseInt(m2.group()));
+			});
+		} else {
+			groups.sort(Comparator.comparing(StudentGroupDetailsDTO::getName));
+		}
+
+		// Filter
+		if (query == null) {
+			return groups;
+		}
+
+		String q = query.toLowerCase();
+		return groups.stream().filter(group -> group.getName().toLowerCase().contains(q)
+				|| group.getMembers().stream().map(RolePersonLayer1DTO::getPerson)
+						.anyMatch(member -> member.getUsername().toLowerCase().contains(q)
+								|| member.getDisplayName().toLowerCase().contains(q)
+								|| member.getNumber().toString().contains(q)))
+				.toList();
+	}
 }
diff --git a/src/main/java/nl/tudelft/submit/service/SubmissionService.java b/src/main/java/nl/tudelft/submit/service/SubmissionService.java
index 72af88b811bc5f60bfeb0be4cf652c816abf2075..8520d881b606d3d0ef189a1ffaf20f538a504fe7 100644
--- a/src/main/java/nl/tudelft/submit/service/SubmissionService.java
+++ b/src/main/java/nl/tudelft/submit/service/SubmissionService.java
@@ -46,6 +46,7 @@ import nl.tudelft.submit.repository.note.SubmissionNoteRepository;
 import org.modelmapper.ModelMapper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.core.io.Resource;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -80,12 +81,14 @@ public class SubmissionService {
 	private SubmissionNoteRepository submissionNoteRepository;
 
 	@Autowired
+	@Lazy
 	private AssignmentService assignmentService;
 	@Autowired
 	private FileService fileService;
 	@Autowired
 	private FeedbackService feedbackService;
 	@Autowired
+	@Lazy
 	private StudentGroupService groupService;
 
 	private final ModelMapper mapper = new ModelMapper();
diff --git a/src/main/java/nl/tudelft/submit/service/VersionService.java b/src/main/java/nl/tudelft/submit/service/VersionService.java
index 3f2f0ff4b04efb525d37e837e8def3519a695895..33b7a4999d0532370dcc80ffe77de894a952ee6f 100644
--- a/src/main/java/nl/tudelft/submit/service/VersionService.java
+++ b/src/main/java/nl/tudelft/submit/service/VersionService.java
@@ -59,10 +59,13 @@ public class VersionService {
 	@Autowired
 	private SubmitGroupService submitGroupService;
 	@Autowired
+	@Lazy
 	private StudentGroupService studentGroupService;
 	@Autowired
+	@Lazy
 	private ModuleService moduleService;
 	@Autowired
+	@Lazy
 	private AssignmentService assignmentService;
 	@Autowired
 	@Lazy
diff --git a/src/main/resources/application.template.yaml b/src/main/resources/application.template.yaml
index 653beabfa5eedd4709ee2bac3eb8767296ede29c..2714eea4bac2d6dcde7585f4f84d422fc3bc5bd8 100644
--- a/src/main/resources/application.template.yaml
+++ b/src/main/resources/application.template.yaml
@@ -41,6 +41,9 @@ spring:
 
   mvc.hiddenmethod.filter.enabled: true
 
+  main:
+    allow-circular-references: true
+
   jpa:
     hibernate:
       ddl-auto: create
diff --git a/src/main/resources/mail/html/added_to_edition.html b/src/main/resources/mail/html/added_to_edition.html
index 3fa698c8bcefe264e0be8df9d4144563f3194cd0..3aa913054c2ff29df1768af16de1a94a93c2c8a7 100644
--- a/src/main/resources/mail/html/added_to_edition.html
+++ b/src/main/resources/mail/html/added_to_edition.html
@@ -18,50 +18,37 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" style="
-    font-family: sans-serif;
-">
-
-<head>
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
-</head>
-
-<body>
-
-<div>
-
-    <div>
-
-        <h1 style="
-            color: #00A6D6;
-            font-size: 2em;
-        ">Submit</h1>
-
-    </div>
-
-    <div>
-
-        <div style="padding: .5em 0;">
-            <h2 th:text="#{|${title}|}"></h2>
-            <p th:text="#{mail.greeting(${name})}"></p>
-            <p th:text="#{edition.added.message(${edition})}"></p>
-        </div>
+<html lang="en" xmlns:th="http://www.thymeleaf.org" style="font-family: sans-serif">
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    </head>
 
+    <body>
         <div>
-            <p style="
-                color: #707070;
-            ">
-                <span th:text="#{mail.notification_preference.before}"></span>
-                <a style="color: #00A6D6; text-decoration: none;" target="_blank"
-                   href="https://submit.tudelft.nl/settings" th:text="#{mail.here}"></a>
-                <span th:text="#{mail.notification_preference.after}"></span>
-            </p>
+            <div>
+                <h1 style="color: #00a6d6; font-size: 2em">Submit</h1>
+            </div>
+
+            <div>
+                <div style="padding: 0.5em 0">
+                    <h2 th:text="#{|${title}|}"></h2>
+                    <p th:text="#{mail.greeting(${name})}"></p>
+                    <p th:text="#{edition.added.message(${edition})}"></p>
+                </div>
+
+                <div>
+                    <p style="color: #707070">
+                        <span th:text="#{mail.notification_preference.before}"></span>
+                        <a
+                            style="color: #00a6d6; text-decoration: none"
+                            target="_blank"
+                            href="https://submit.tudelft.nl/settings"
+                            th:text="#{mail.here}"></a>
+                        <span th:text="#{mail.notification_preference.after}"></span>
+                    </p>
+                </div>
+            </div>
         </div>
-
-    </div>
-
-</div>
-
-</body>
-</html>
\ No newline at end of file
+    </body>
+</html>
diff --git a/src/main/resources/mail/html/default.html b/src/main/resources/mail/html/default.html
index 62c48190c1603e9c6c83821973e3eefe63a300a4..5eff7e2388b7438ab1288c5a8a2f972b0f4f1259 100644
--- a/src/main/resources/mail/html/default.html
+++ b/src/main/resources/mail/html/default.html
@@ -18,50 +18,37 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" style="
-    font-family: sans-serif;
-">
-
-<head>
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
-</head>
-
-<body>
-
-<div>
-
-    <div>
-
-        <h1 style="
-            color: #00A6D6;
-            font-size: 2em;
-        ">Submit</h1>
-
-    </div>
-
-    <div>
-
-        <div style="padding: .5em 0;">
-            <h2 th:text="#{|${title}|}"></h2>
-            <p th:text="#{mail.greeting(${name})}"></p>
-            <p th:text="#{|${message}|}"></p>
-        </div>
+<html lang="en" xmlns:th="http://www.thymeleaf.org" style="font-family: sans-serif">
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    </head>
 
+    <body>
         <div>
-            <p style="
-                color: #707070;
-            ">
-                <span th:text="#{mail.notification_preference.before}"></span>
-                <a style="color: #00A6D6; text-decoration: none;" target="_blank"
-                   href="https://submit.tudelft.nl/settings" th:text="#{mail.here}"></a>
-                <span th:text="#{mail.notification_preference.after}"></span>
-            </p>
+            <div>
+                <h1 style="color: #00a6d6; font-size: 2em">Submit</h1>
+            </div>
+
+            <div>
+                <div style="padding: 0.5em 0">
+                    <h2 th:text="#{|${title}|}"></h2>
+                    <p th:text="#{mail.greeting(${name})}"></p>
+                    <p th:text="#{|${message}|}"></p>
+                </div>
+
+                <div>
+                    <p style="color: #707070">
+                        <span th:text="#{mail.notification_preference.before}"></span>
+                        <a
+                            style="color: #00a6d6; text-decoration: none"
+                            target="_blank"
+                            href="https://submit.tudelft.nl/settings"
+                            th:text="#{mail.here}"></a>
+                        <span th:text="#{mail.notification_preference.after}"></span>
+                    </p>
+                </div>
+            </div>
         </div>
-
-    </div>
-
-</div>
-
-</body>
-</html>
\ No newline at end of file
+    </body>
+</html>
diff --git a/src/main/resources/mail/html/grade_updated.html b/src/main/resources/mail/html/grade_updated.html
index 5a8f380b708a8f8867d14677f9b6a119d40afa72..bb650d34057b0b5ea730a67638f7257f772a81d9 100644
--- a/src/main/resources/mail/html/grade_updated.html
+++ b/src/main/resources/mail/html/grade_updated.html
@@ -18,50 +18,37 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" style="
-    font-family: sans-serif;
-">
-
-<head>
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
-</head>
-
-<body>
-
-<div>
-
-    <div>
-
-        <h1 style="
-            color: #00A6D6;
-            font-size: 2em;
-        ">Submit</h1>
-
-    </div>
-
-    <div>
-
-        <div style="padding: .5em 0;">
-            <h2 th:text="#{|${title}|}"></h2>
-            <p th:text="#{mail.greeting(${name})}"></p>
-            <p th:text="#{grade.updated.message(${grade}, ${entity})}"></p>
-        </div>
+<html lang="en" xmlns:th="http://www.thymeleaf.org" style="font-family: sans-serif">
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    </head>
 
+    <body>
         <div>
-            <p style="
-                color: #707070;
-            ">
-                <span th:text="#{mail.notification_preference.before}"></span>
-                <a style="color: #00A6D6; text-decoration: none;" target="_blank"
-                   href="https://submit.tudelft.nl/settings" th:text="#{mail.here}"></a>
-                <span th:text="#{mail.notification_preference.after}"></span>
-            </p>
+            <div>
+                <h1 style="color: #00a6d6; font-size: 2em">Submit</h1>
+            </div>
+
+            <div>
+                <div style="padding: 0.5em 0">
+                    <h2 th:text="#{|${title}|}"></h2>
+                    <p th:text="#{mail.greeting(${name})}"></p>
+                    <p th:text="#{grade.updated.message(${grade}, ${entity})}"></p>
+                </div>
+
+                <div>
+                    <p style="color: #707070">
+                        <span th:text="#{mail.notification_preference.before}"></span>
+                        <a
+                            style="color: #00a6d6; text-decoration: none"
+                            target="_blank"
+                            href="https://submit.tudelft.nl/settings"
+                            th:text="#{mail.here}"></a>
+                        <span th:text="#{mail.notification_preference.after}"></span>
+                    </p>
+                </div>
+            </div>
         </div>
-
-    </div>
-
-</div>
-
-</body>
-</html>
\ No newline at end of file
+    </body>
+</html>
diff --git a/src/main/resources/mail/html/group.html b/src/main/resources/mail/html/group.html
index 43b68a11e5d29fad8bfeeadad6d3c3780b0a9ff3..7379cb6ed136de42b84540d1fdcce4cd9efd85b6 100644
--- a/src/main/resources/mail/html/group.html
+++ b/src/main/resources/mail/html/group.html
@@ -18,48 +18,35 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" style="
-    font-family: sans-serif;
-">
-
-<head>
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
-</head>
-
-<body>
-
-<div>
-
-    <div>
-
-        <h1 style="
-            color: #00A6D6;
-            font-size: 2em;
-        ">Submit</h1>
-
-    </div>
-
-    <div>
-
-        <div style="padding: .5em 0;">
-            <p th:text="${message}"></p>
-        </div>
+<html lang="en" xmlns:th="http://www.thymeleaf.org" style="font-family: sans-serif">
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    </head>
 
+    <body>
         <div>
-            <p style="
-                color: #707070;
-            ">
-                <span th:text="#{mail.notification_preference.before}"></span>
-                <a style="color: #00A6D6; text-decoration: none;" target="_blank"
-                   href="https://submit.tudelft.nl/settings" th:text="#{mail.here}"></a>
-                <span th:text="#{mail.notification_preference.after}"></span>
-            </p>
+            <div>
+                <h1 style="color: #00a6d6; font-size: 2em">Submit</h1>
+            </div>
+
+            <div>
+                <div style="padding: 0.5em 0">
+                    <p th:text="${message}"></p>
+                </div>
+
+                <div>
+                    <p style="color: #707070">
+                        <span th:text="#{mail.notification_preference.before}"></span>
+                        <a
+                            style="color: #00a6d6; text-decoration: none"
+                            target="_blank"
+                            href="https://submit.tudelft.nl/settings"
+                            th:text="#{mail.here}"></a>
+                        <span th:text="#{mail.notification_preference.after}"></span>
+                    </p>
+                </div>
+            </div>
         </div>
-
-    </div>
-
-</div>
-
-</body>
-</html>
\ No newline at end of file
+    </body>
+</html>
diff --git a/src/main/resources/mail/html/submission_received.html b/src/main/resources/mail/html/submission_received.html
index d730847543a4564c4603b0ab85b336c229190329..88a02671206d160cc4d5ffa126e8e0bc3e180fe9 100644
--- a/src/main/resources/mail/html/submission_received.html
+++ b/src/main/resources/mail/html/submission_received.html
@@ -18,56 +18,47 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" style="
-    font-family: sans-serif;
-">
+<html lang="en" xmlns:th="http://www.thymeleaf.org" style="font-family: sans-serif">
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    </head>
 
-<head>
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
-</head>
-
-<body>
-
-<div>
-
-    <div>
-
-        <h1 style="
-            color: #00A6D6;
-            font-size: 2em;
-        ">Submit</h1>
-
-    </div>
-
-    <div>
-
-        <div style="padding: .5em 0;">
-            <h2 th:text="#{submission.received.title}"></h2>
-            <p th:text="#{mail.greeting(${name})}"></p>
-            <p th:if="${filenames.size() == 1}" th:text="#{submission.received.message(${filenames[0]})}"></p>
-            <div th:if="${filenames.size() > 1}">
-                <p th:text="#{submission.received.message_multiple}"></p>
-                <ul>
-                    <li th:each="filename : ${filenames}" th:text="|&#39;${filename}&#39;|"></li>
-                </ul>
+    <body>
+        <div>
+            <div>
+                <h1 style="color: #00a6d6; font-size: 2em">Submit</h1>
             </div>
-        </div>
 
-        <div>
-            <p style="
-                color: #707070;
-            ">
-                <span th:text="#{mail.notification_preference.before}"></span>
-                <a style="color: #00A6D6; text-decoration: none;" target="_blank"
-                   href="https://submit.tudelft.nl/settings" th:text="#{mail.here}"></a>
-                <span th:text="#{mail.notification_preference.after}"></span>
-            </p>
+            <div>
+                <div style="padding: 0.5em 0">
+                    <h2 th:text="#{submission.received.title}"></h2>
+                    <p th:text="#{mail.greeting(${name})}"></p>
+                    <p
+                        th:if="${filenames.size() == 1}"
+                        th:text="#{submission.received.message(${filenames[0]})}"></p>
+                    <div th:if="${filenames.size() > 1}">
+                        <p th:text="#{submission.received.message_multiple}"></p>
+                        <ul>
+                            <li
+                                th:each="filename : ${filenames}"
+                                th:text="|&#39;${filename}&#39;|"></li>
+                        </ul>
+                    </div>
+                </div>
+
+                <div>
+                    <p style="color: #707070">
+                        <span th:text="#{mail.notification_preference.before}"></span>
+                        <a
+                            style="color: #00a6d6; text-decoration: none"
+                            target="_blank"
+                            href="https://submit.tudelft.nl/settings"
+                            th:text="#{mail.here}"></a>
+                        <span th:text="#{mail.notification_preference.after}"></span>
+                    </p>
+                </div>
+            </div>
         </div>
-
-    </div>
-
-</div>
-
-</body>
-</html>
\ No newline at end of file
+    </body>
+</html>
diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties
index 4615a8c06510dff4123f1bf87088a66035709cac..449fda6a6b91fc50c18f017d8edd5939fe4605e5 100644
--- a/src/main/resources/messages.properties
+++ b/src/main/resources/messages.properties
@@ -41,7 +41,7 @@ general.headers = Headers
 general.headers.help = If the first line of the file consists of headers (e.g. "username,studentnumber,role"), check this box.
 general.headers.enter = File has headers
 general.file = File
-general.progress_overview = Progress overview
+general.progress_overview = Progress
 general.description = Description
 general.description.enter = Enter description
 general.allow = Allow
@@ -102,7 +102,7 @@ edition.end_date.enter = Enter end date (dd-mm-yyyy HH:mm)
 edition.export_submissions = Export submissions
 edition.hide = Hide edition
 edition.hide.confirm = Are you sure you want to hide {0} in Submit?
-edition.hide.info = This edition is hidden. It cannot be seen in overviews by students. If this edition uses Submit and should not be hidden, you can make it visible it in the sidebar on the left (Show edition).
+edition.hide.info = This edition is hidden. It cannot be seen in overviews by students. If this edition uses Submit and should not be hidden, you can make it visible by pressing 'Show edition' below.
 edition.show = Show edition
 edition.show.confirm = Are you sure you want to show {0} in Submit?
 
@@ -127,7 +127,7 @@ edition.enrollability.closed = Closed (manual import)
 
 module.add = Add new module
 module.edit = Edit module
-module.groups_and_grading = Groups and grading
+module.groups_and_grading = Groups
 module.assignments = Assignments
 module.divisions = Divisions
 module.grading_formula = Module grading formula
diff --git a/src/main/resources/migrations.yaml b/src/main/resources/migrations.yaml
index bf64fd501c934b29cb75d4e711af4b3ca706d6b6..4317e3982037620887403e86d4bf8fe02ab7fa68 100644
--- a/src/main/resources/migrations.yaml
+++ b/src/main/resources/migrations.yaml
@@ -504,4 +504,28 @@ databaseChangeLog:
                   name: key
                   type: VARCHAR(256)
             tableName: edition_api_key
-
+  - changeSet:
+      id: column-name-should-not-be-key
+      author: Ruben
+      changes:
+        - renameColumn:
+            oldColumnName: key
+            newColumnName: api_key
+            columnDataType: VARCHAR(256)
+            tableName: edition_api_key
+  - changeSet:
+      id: column-name-should-not-be-key-2
+      author: Ruben
+      changes:
+        - renameColumn:
+            oldColumnName: key
+            newColumnName: api_key
+            columnDataType: VARCHAR(255)
+            tableName: submission_key
+  - changeSet:
+      id: can-grade-was-removed
+      author: Ruben
+      changes:
+        - dropColumn:
+            tableName: submission_key
+            columnName: can_grade
diff --git a/src/main/resources/scss/_elements.scss b/src/main/resources/scss/_elements.scss
deleted file mode 100644
index bf13318d6e69295f62c565689e42835034cf3c32..0000000000000000000000000000000000000000
--- a/src/main/resources/scss/_elements.scss
+++ /dev/null
@@ -1,707 +0,0 @@
-@mixin horizontal-bar($colour) {
-  background-color: $colour;
-  content: '';
-  height: 1px;
-  left: 0;
-  position: absolute;
-  width: 100%;
-}
-
-.icon-button {
-  background: none;
-  border: 1px solid var(--primary-blue);
-  border-radius: 100%;
-  color: var(--primary-blue);
-  cursor: pointer;
-  font-size: .8rem;
-  height: 2.2em;
-  width: 2.2em;
-
-  &.warning {
-    border: 1px solid var(--primary-red);
-    color: var(--primary-red);
-  }
-  &:hover, &:active {
-    background-color: var(--primary-blue);
-    color: var(--primary-light);
-  }
-  &.warning:hover {
-    background-color: var(--primary-red);
-  }
-  &:disabled {
-    background: none;
-    border-color: var(--primary-grey);
-    color: var(--primary-grey);
-    cursor: initial;
-  }
-}
-
-.text-button {
-  background: none;
-  border: none;
-  color: var(--primary-blue);
-  cursor: pointer;
-  margin: auto 0;
-  text-decoration: none;
-
-  &.warning {
-    color: var(--primary-red);
-  }
-  &:hover {
-    color: var(--secondary-blue);
-  }
-  &.warning:hover {
-    color: var(--secondary-red);
-  }
-  &:disabled, &:disabled:hover {
-    color: var(--primary-grey);
-    cursor: initial;
-  }
-}
-
-.title-and-search {
-  align-items: center;
-  display: flex;
-  justify-content: space-between;
-  margin-bottom: 2rem;
-  margin-top: 3rem;
-
-  &_elements {
-    display: flex;
-    > * + * {
-      margin-left: 1rem;
-    }
-  }
-
-  .selectbox {
-    height: 3em;
-    padding: 0 1em;
-  }
-
-  .search {
-    background-color: var(--primary-light);
-    display: flex;
-    height: 3em;
-    width: 23.7rem;
-
-    &_field {
-      border: none;
-      border-radius: 0;
-      font-style: italic;
-      padding: 0 2em;
-      width: 100%;
-    }
-
-    &_button {
-      background-color: var(--primary-blue);
-      border: none;
-      border-radius: 0;
-      color: white;
-      cursor: pointer;
-      font-size: 1.3rem;
-      min-width: 3rem;
-      transform: scaleX(-1);
-
-      &:hover {
-        background-color: var(--primary-light);
-        color: var(--primary-blue);
-      }
-    }
-  }
-}
-.breadcrumbs + * .title-and-search {
-  margin-top: 0;
-}
-.title + .title-and-search,
-.title-and-options + .title-and-search,
-.tabs + .title-and-search {
-  margin-top: 2rem;
-}
-
-.title-and-options {
-  align-items: flex-end;
-  display: flex;
-  flex-wrap: wrap;
-  justify-content: space-between;
-
-  &_options {
-    display: flex;
-    > * + * {
-      margin-left: 2rem;
-    }
-  }
-}
-
-.table {
-  border-collapse: collapse;
-  font-size: var(--table-size);
-  position: relative;
-  text-align: left;
-  width: 100%;
-
-  --header-height: 1.4rem;
-
-  &_header {
-    th, td {
-      border-bottom: 3px solid var(--primary-dark);
-      border-top: 1px solid var(--primary-grey);
-      padding: var(--header-height) 0;
-    }
-    th:first-child, td:first-child {
-      padding: var(--header-height) 0 var(--header-height) var(--header-height);
-    }
-    th:last-child, td:last-child {
-      padding: var(--header-height) var(--header-height) var(--header-height) 0;
-    }
-  }
-
-  &_actions {
-    display: flex;
-    justify-content: flex-end;
-    > * + * {
-      margin-left: 2rem;
-    }
-    > .icon-button + .icon-button {
-      margin-left: .5rem;
-    }
-  }
-
-  &_cell-with-buttons {
-    display: flex;
-    > span {
-      align-items: center;
-      display: flex;
-    }
-    > * + * {
-      margin-left: .5rem;
-    }
-  }
-
-  td:first-child {
-    padding: 1rem 0 1rem var(--header-height);
-  }
-  td:last-child {
-    padding: 1rem var(--header-height) 1rem 0;
-  }
-
-  tr + tr {
-    td {
-      border-top: 1px solid var(--primary-grey);
-    }
-  }
-}
-.subtitle + .table {
-  margin-top: 1rem;
-}
-
-.list {
-  display: flex;
-  flex-direction: column;
-  margin-top: 1rem;
-
-  &_item {
-    display: flex;
-    justify-content: space-between;
-    padding: 1rem 2rem;
-    position: relative;
-
-    &_content {
-      font-size: var(--list-content-size);
-    }
-
-    &::after {
-      @include horizontal-bar(var(--primary-grey));
-      bottom: 0;
-    }
-
-    .fa-chevron-down {
-      transition: transform 100ms ease-in-out;
-    }
-    &:not(.collapsed) .fa-chevron-down {
-      transform: rotate(180deg);
-    }
-
-    &:not(.collapsed) + .list_subitems {
-      max-height: initial;
-      transform: scaleY(1);
-      transition: transform 100ms ease-in-out;
-    }
-  }
-  & > .list_item, .collapsed.list_item {
-    &::after {
-      background-color: var(--primary-blue);
-    }
-  }
-
-  &_subitems {
-    font-size: var(--regular-size);
-    max-height: 0;
-    transform: scaleY(0);
-    transform-origin: top;
-  }
-
-  &_subitem {
-    display: flex;
-    justify-content: space-between;
-    padding: 1rem 2rem;
-    position: relative;
-
-    &_content {
-      font-size: var(--table-size);
-    }
-
-    &::after {
-      @include horizontal-bar(var(--primary-grey));
-      bottom: 0;
-    }
-
-    &:last-child::after {
-      background-color: var(--primary-blue);
-    }
-  }
-
-  &_actions {
-    display: flex;
-    font-size: var(--table-size);
-    > * + * {
-      margin-left: 2rem;
-    }
-  }
-  &_subitem > .list_actions > * {
-    font-size: var(--regular-size);
-  }
-}
-
-.half {
-  flex: 1 1;
-}
-
-.card {
-  align-items: center;
-  background-color: var(--primary-light);
-  display: flex;
-  font-size: var(--table-size);
-  justify-content: center;
-  min-height: 8.25rem;
-  position: relative;
-  text-decoration: none;
-  width: 24rem;
-
-  &::after {
-    background-color: var(--primary-blue);
-    bottom: 0;
-    content: '';
-    height: 2px;
-    left: 0;
-    position: absolute;
-    transform-origin: bottom;
-    width: 100%;
-  }
-
-  &.interactive {
-    border: none;
-    color: var(--primary-blue);
-    cursor: pointer;
-    flex-direction: column;
-  }
-
-  &.interactive:hover {
-    background-color: var(--primary-grey);
-    &::after {
-      transform: scaleY(2);
-    }
-  }
-
-  &_icon {
-    font-size: 3rem;
-    margin-bottom: .5rem;
-  }
-
-  &_top {
-    align-self: flex-start;
-  }
-
-  &_vertical-content {
-    align-content: flex-start;
-    display: flex;
-    flex-direction: column;
-    font-size: var(--list-content-size);
-    padding: 1rem 2rem;
-    width: 100%;
-    & > *:first-child {
-      font-size: var(--subtitle-size);
-    }
-    &.smaller {
-      font-size: var(--regular-size);
-    }
-    &.smaller > *:first-child {
-      font-size: var(--regular-size);
-      font-weight: bold;
-      & + * {
-        margin-top: 1.5rem;
-      }
-    }
-  }
-
-  &_content-with-icon {
-    align-items: center;
-    display: flex;
-    justify-content: space-between;
-    padding: 1rem 2rem;
-    width: 100%;
-    > * {
-      align-content: center;
-    }
-    & > .icon-button {
-      font-size: var(--regular-size);
-      min-width: 2.2em;
-    }
-  }
-}
-
-.overlay {
-  align-items: center;
-  background-color: rgba(0, 0, 0, 0.5);
-  display: flex;
-  justify-content: center;
-  height: 100%;
-  left: 0;
-  position: absolute;
-  top: 0;
-  width: 100%;
-  z-index: 10;
-}
-
-.boxed-content {
-  background-color: var(--background-colour);
-  border: 1px solid var(--primary-grey);
-  max-height: 100%;
-  overflow-y: auto;
-  padding: 2em;
-}
-
-.overlay > .boxed-content {
-  min-width: 50vw;
-}
-
-.underlined {
-  position: relative;
-  &::after {
-    @include horizontal-bar(var(--primary-grey));
-    bottom: -.25rem;
-  }
-}
-
-.confirm {
-  & > .boxed-content {
-    min-width: initial;
-  }
-  &_text {
-    font-size: var(--list-content-size);
-    text-align: center;
-  }
-}
-
-.form-vstack {
-  display: flex;
-  flex-direction: column;
-
-  > * + * {
-    margin-top: .25rem;
-  }
-}
-
-.form-hstack {
-  display: flex;
-  align-items: center;
-
-  > * + * {
-    margin-left: 1rem;
-  }
-}
-
-.form-hstack-grid {
-  display: grid;
-  gap: 1rem;
-  grid-template-columns: repeat(auto-fit, minmax(20%, 1fr));
-}
-
-.form-group {
-  display: grid;
-  font-size: var(--table-size);
-  grid-template-columns: auto 1fr;
-  grid-row-gap: .5rem;
-  grid-column-gap: 4rem;
-  padding: 2rem 1rem;
-  position: relative;
-  &::after {
-    @include horizontal-bar(var(--primary-grey));
-    bottom: 0;
-  }
-}
-
-.form-separator {
-  margin: 1.5rem 0;
-  grid-column: auto / span 2;
-  position: relative;
-  &::after {
-    @include horizontal-bar(var(--primary-grey));
-    bottom: 0;
-  }
-}
-
-.form-buttons {
-  display: flex;
-  justify-content: space-between;
-  padding: 2rem 1rem;
-  &:last-child {
-    padding: 2rem 1rem 0 1rem;
-  }
-}
-
-.textfield {
-  background-color: var(--primary-light);
-  border: none;
-  border-radius: 0;
-  font-style: italic;
-  height: 100%;
-  padding: 1rem;
-}
-
-.textarea {
-  background-color: var(--primary-light);
-  border: none;
-  border-radius: 0;
-  font-family: var(--font);
-  font-style: italic;
-  height: 100%;
-  padding: 1rem;
-  resize: vertical;
-}
-
-.selectbox {
-  background-color: var(--primary-light);
-  border: none;
-  border-radius: 0;
-  color: var(--primary-blue);
-  font-size: var(--regular-size);
-  height: 100%;
-  padding: .5rem .75rem;
-  position: relative;
-}
-
-.checkbox {
-  input[type=checkbox] {
-    display: none;
-  }
-
-  label {
-    color: var(--primary-blue);
-    cursor: pointer;
-    font-size: var(--table-size);
-    padding-left: 1.5em;
-    position: relative;
-
-    &::before {
-      border: 1px solid var(--primary-blue);
-      border-radius: 3px;
-      content: '';
-      height: 1em;
-      left: 0;
-      position: absolute;
-      top: 0;
-      width: 1em;
-    }
-    &::after {
-      content: '';
-      font-size: 1.25em;
-      left: .05em;
-      position: absolute;
-      top: -.15em;
-    }
-
-    &:hover {
-      color: var(--secondary-blue);
-      &::before {
-        border: 1px solid var(--secondary-blue);
-      }
-    }
-  }
-
-  input[type=checkbox]:checked ~ label::after {
-    content: '\2713';
-  }
-}
-
-.tabs {
-  border-bottom: 1px solid var(--primary-grey);
-  display: flex;
-  font-size: var(--regular-size);
-  width: 100%;
-}
-.title-and-search + .tabs {
-  border-top: 1px solid var(--primary-grey);
-}
-
-.tab {
-  background: none;
-  border: none;
-  color: var(--primary-dark);
-  cursor: pointer;
-  display: block;
-  padding-top: 1.8rem;
-  padding-bottom: 1.2rem;
-  position: relative;
-  text-align: center;
-  text-decoration: none;
-  width: 12.5rem;
-
-  &::after {
-    @include horizontal-bar(var(--primary-blue));
-    bottom: -2px;
-    height: 3px;
-    transform: scaleX(0);
-    z-index: 1;
-  }
-  &.active::after, &:hover::after {
-    transform: scaleX(1);
-  }
-  & + .tab {
-    margin-left: .5rem;
-  }
-}
-
-.tooltip {
-  display: inline;
-  margin-left: .5rem;
-  position: relative;
-
-  &_icon {
-    border: 2px solid var(--primary-blue);
-    border-radius: 100%;
-    color: var(--primary-blue);
-    cursor: pointer;
-    display: inline-block;
-    height: 1.25em;
-    text-align: center;
-    width: 1.25em;
-
-    &:hover + .tooltip_text {
-      transform: scaleY(1);
-    }
-  }
-
-  &_text {
-    background-color: var(--primary-light);
-    border: 1px solid var(--primary-grey);
-    box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.25);
-    display: block;
-    font-size: var(--regular-size);
-    padding: .25rem;
-    position: absolute;
-    transform: scaleY(0);
-    transform-origin: top;
-    transition: transform 50ms ease-in-out;
-    width: 30rem;
-    z-index: 15;
-
-    &:hover {
-      transform: scaleY(1);
-    }
-  }
-}
-
-.info_row {
-  display: flex;
-  margin: 2rem 0;
-
-  & > .card + .card {
-    margin-left: 2rem;
-  }
-
-  @media(max-width: 1200px) {
-    flex-direction: column;
-
-    & > .card {
-      width: initial;
-    }
-    & > .card + .card {
-      margin-left: 0;
-      margin-top: 2rem;
-    }
-  }
-}
-
-.card_stats {
-  flex-direction: column;
-  flex-grow: 1;
-  padding: 0 2em;
-
-  &_row {
-    display: flex;
-    justify-content: space-between;
-    position: relative;
-    width: 100%;
-
-    &.compact {
-      justify-content: initial;
-      > * {
-        flex: 1 1;
-      }
-    }
-
-    &:not(.compact)::after {
-      background-color: var(--primary-grey);
-      bottom: 0;
-      content: '';
-      height: 1px;
-      left: 0;
-      position: absolute;
-      width: 100%;
-    }
-  }
-
-  > *:not(.compact) + * {
-    margin-top: 1rem;
-  }
-}
-
-.tag {
-  background-color: var(--primary-blue);
-  color: var(--primary-light);
-  float: left;
-  font-size: var(--regular-size);
-  margin-bottom: .25rem;
-  margin-right: .5rem;
-
-  padding: .25em .5em;
-}
-
-.announcement {
-  align-self: center;
-  background-color: white;
-  display: flex;
-  margin-top: 1rem;
-  padding: .75rem 1rem;
-  position: relative;
-  width: var(--body-width);
-
-  &_accent {
-    height: 100%;
-    left: 0;
-    position: absolute;
-    top: 0;
-    width: 4px;
-  }
-
-  &_text {
-    margin: auto 0;
-    width: 100%;
-  }
-
-  &.in_content {
-    width: 100%;
-    margin-bottom: 1em;
-    margin-top: 0;
-  }
-}
\ No newline at end of file
diff --git a/src/main/resources/scss/_footer.scss b/src/main/resources/scss/_footer.scss
deleted file mode 100644
index 621bca76398d25f206907c8b6912b4159fa35787..0000000000000000000000000000000000000000
--- a/src/main/resources/scss/_footer.scss
+++ /dev/null
@@ -1,14 +0,0 @@
-.footer {
-  background-color: var(--primary-light);
-  height: var(--footer-height);
-  font-size: .9rem;
-  width: 100%;
-
-  &_content {
-    display: flex;
-    height: 100%;
-    margin: 0 auto;
-    max-width: var(--body-width);
-    width: 100%;
-  }
-}
\ No newline at end of file
diff --git a/src/main/resources/scss/_header.scss b/src/main/resources/scss/_header.scss
deleted file mode 100644
index f403cac1902ed2ca344fe83a41c14bfdc26083cf..0000000000000000000000000000000000000000
--- a/src/main/resources/scss/_header.scss
+++ /dev/null
@@ -1,175 +0,0 @@
-.header {
-  color: var(--primary-light);
-  background-color: var(--primary-blue);
-  height: var(--header-height);
-  width: 100%;
-
-  @media(max-width: 768px) {
-    height: initial;
-  }
-
-  &_content {
-    display: flex;
-    justify-content: space-between;
-    height: 100%;
-    margin: 0 auto;
-    max-width: var(--body-width);
-    width: 100%;
-    @media(max-width: 768px) {
-      justify-content: flex-start;
-      flex-direction: column;
-      padding: .5rem 0;
-    }
-  }
-
-  &_logo {
-    align-self: flex-end;
-    color: var(--primary-light);
-    font-size: 2rem;
-    margin-bottom: 1rem;
-    text-decoration: none;
-    @media(max-width: 768px) {
-      align-self: initial;
-      margin-bottom: .5rem;
-    }
-  }
-
-  &_controls {
-    align-items: center;
-    display: flex;
-    height: 100%;
-    @media(max-width: 768px) {
-      flex-direction: row;
-      justify-content: space-between;
-      margin-bottom: .5rem;
-    }
-  }
-
-  &_links {
-    > * {
-      color: var(--primary-light);
-      margin-right: 2em;
-      text-decoration: none;
-      &:hover {
-        text-decoration: underline;
-      }
-    }
-  }
-
-  &_buttons {
-    display: flex;
-    justify-content: center;
-    > * + * {
-      margin-left: 0.8rem;
-    }
-  }
-
-  &_icon {
-    align-items: center;
-    background: none;
-    border: 1px solid var(--primary-light);
-    border-radius: 100%;
-    color: var(--primary-light);
-    cursor: pointer;
-    display: flex;
-    font-size: .8rem;
-    height: 2.2em;
-    justify-content: center;
-    text-decoration: none;
-    width: 2.2em;
-
-    & > button {
-      background: none;
-      border: none;
-      color: var(--primary-light);
-      cursor: pointer;
-      font-size: .8rem;
-    }
-
-    &:hover, &:active {
-      background-color: var(--primary-light);
-      color: var(--primary-blue);
-    }
-    &:hover > button, &:active > button {
-      color: var(--primary-blue);
-    }
-  }
-
-  &_notifications {
-    position: relative;
-
-    &_amount {
-      background-color: var(--primary-blue);
-      border: 1px solid var(--primary-light);
-      border-radius: 100%;
-      color: var(--primary-light);
-      display: block;
-      font-size: .60rem;
-      height: 1rem;
-      position: absolute;
-      right: -.25rem;
-      text-align: center;
-      top: -.25rem;
-      width: 1rem;
-    }
-  }
-
-  &_dropdown {
-    position: relative;
-
-    &_content {
-      border: 1px solid var(--primary-grey);
-      background-color: var(--primary-light);
-      position: absolute;
-      display: none;
-      flex-direction: column;
-      z-index: 1;
-      left: -100%;
-
-      a {
-        color: var(--primary-blue);
-        text-decoration: none;
-        padding: .5rem 1rem;
-
-        &:hover {
-          color: var(--secondary-blue);
-        }
-      }
-    }
-
-    &:hover > &_content,
-    &:focus-within > &_content,
-    & > &_content:hover {
-      display: flex;
-    }
-  }
-}
-
-#cog {
-  & > * {
-    transform: rotate(0);
-    transition: transform ease-in-out 250ms;
-  }
-  &:hover > * {
-    transform: rotate(135deg) translateX(-.05rem) translateY(.05rem);
-  }
-}
-
-#bell {
-  & > * {
-    transform-origin: top;
-  }
-  &:hover, &:focus {
-    & > * {
-      animation: ring 200ms;
-    }
-  }
-}
-
-@keyframes ring {
-  0% { transform: rotate(0deg); }
-  25% { transform: rotate(10deg); }
-  50% { transform: rotate(0deg); }
-  75% { transform: rotate(-10deg); }
-  100% { transform: rotate(0deg); }
-}
\ No newline at end of file
diff --git a/src/main/resources/scss/_sidebar.scss b/src/main/resources/scss/_sidebar.scss
deleted file mode 100644
index eab7dea11aae6a19fc5d8f7508a6f91ecec77225..0000000000000000000000000000000000000000
--- a/src/main/resources/scss/_sidebar.scss
+++ /dev/null
@@ -1,69 +0,0 @@
-.sidebar {
-  display: flex;
-  flex-direction: column;
-  min-width: 20%;
-  margin-right: 2rem;
-  margin-top: 2rem;
-  @media(max-width: 768px) {
-    margin-right: 0;
-  }
-
-  &_item {
-    background: none;
-    border: none;
-    color: var(--primary-blue);
-    font-size: var(--regular-size);
-    padding: 1em 0;
-    cursor: pointer;
-    position: relative;
-    text-align: left;
-    text-decoration: none;
-
-    &.warning {
-      color: var(--primary-red);
-    }
-
-    &::after {
-      background-color: var(--primary-light);
-      content: '';
-      height: 100%;
-      left: -10px;
-      position: absolute;
-      top: 0;
-      transform: scaleX(0);
-      transform-origin: left;
-      width: calc(100% + 10px);
-      z-index: -1;
-    }
-
-    &:hover {
-      &::before {
-        background-color: var(--primary-blue);
-        content: '';
-        height: 100%;
-        left: -16px;
-        position: absolute;
-        top: 0;
-        width: 6px;
-      }
-      &::after {
-        transform: scaleX(1);
-        transition: transform 200ms cubic-bezier(.56,0,0,1.6);
-      }
-    }
-
-    &:disabled {
-      color: var(--primary-grey);
-      cursor: initial;
-      &::before, &::after {
-        content: none;
-      }
-    }
-  }
-
-  &_group {
-    display: flex;
-    flex-direction: column;
-    margin-left: 2rem;
-  }
-}
\ No newline at end of file
diff --git a/src/main/resources/scss/_utility.scss b/src/main/resources/scss/_utility.scss
deleted file mode 100644
index eddc003a3a4da31164828bcbe17a5220f6534352..0000000000000000000000000000000000000000
--- a/src/main/resources/scss/_utility.scss
+++ /dev/null
@@ -1,19 +0,0 @@
-$fs: (300: 12pt, 400: 14pt, 500: 16pt, 600: 20pt, 700: 24pt, 900: 42pt);
-@each $size, $value in $fs {
-  .fs_#{$size} {
-    font-size: $value;
-  }
-}
-
-.flex_horizontal {
-  display: flex;
-  gap: var(--gap, 1rem);
-}
-.flex_vertical {
-  display: flex;
-  flex-direction: column;
-  gap: var(--gap, 1rem);
-}
-.flex_grow {
-  flex-grow: 1;
-}
\ No newline at end of file
diff --git a/src/main/resources/scss/_vars.scss b/src/main/resources/scss/_vars.scss
deleted file mode 100644
index ba314c5f7253aed4ee74eb9a46477b50876e00e2..0000000000000000000000000000000000000000
--- a/src/main/resources/scss/_vars.scss
+++ /dev/null
@@ -1,40 +0,0 @@
-@font-face {
-  font-family: Roboto;
-  src: url("/font/Roboto-Thin.ttf");
-}
-
-:root {
-  --primary-blue: #00A8DB;
-  --primary-light: #FFFFFF;
-  --primary-dark: #000000;
-  --primary-grey: #CACACA;
-  --primary-red: #C3312F;
-
-  --secondary-blue: #003A73;
-  --secondary-green: #21BA46;
-  --secondary-red: #731C1C;
-  --secondary-orange: #EB7245;
-  --secondary-cyan: #00a390;
-  --secondary-yellow: #FFDB4F;
-  --secondary-orange-light: #FF9F4F;
-
-  --input-grey: #9A9A9A;
-
-  --background-colour: #F7F7F7;
-
-  --font: arial, sans-serif;
-  --title-font: Roboto, sans-serif;
-
-  --title-size: 42pt;
-  --subtitle-size: 24pt;
-  --list-content-size: 16pt;
-  --table-size: 14pt;
-  --regular-size: 12pt;
-
-  --body-width: calc(min(1200px, 90vw));
-  --header-height: 70px;
-  --footer-height: 50px;
-
-  background-color: var(--background-colour);
-  font-family: sans-serif;
-}
diff --git a/src/main/resources/scss/article.scss b/src/main/resources/scss/article.scss
deleted file mode 100644
index 79620457dffba7c8b62337efc10d2c80630ee42f..0000000000000000000000000000000000000000
--- a/src/main/resources/scss/article.scss
+++ /dev/null
@@ -1,37 +0,0 @@
-p, ul {
-  & + ul {
-    margin-top: .5rem;
-  }
-  & + p {
-    margin-top: 1rem;
-  }
-  & + h2 {
-    margin-top: 1.5rem;
-  }
-}
-
-.title, h2 {
-  margin-bottom: .5rem;
-}
-
-p, ul {
-  line-height: 1.2rem;
-}
-
-ul {
-  margin-left: 2rem;
-}
-
-.content a {
-  color: var(--primary-blue);
-  text-decoration: none;
-  &:hover {
-    color: var(--secondary-blue);
-  }
-}
-
-code {
-  background-color: var(--primary-light);
-  border: 1px solid var(--primary-grey);
-  padding: 0 .4rem;
-}
\ No newline at end of file
diff --git a/src/main/resources/scss/edition.scss b/src/main/resources/scss/edition.scss
deleted file mode 100644
index f3c747756a8cf116feb5bcc4ec794be1ca126583..0000000000000000000000000000000000000000
--- a/src/main/resources/scss/edition.scss
+++ /dev/null
@@ -1,102 +0,0 @@
-@import 'vars';
-
-.edition_modules {
-  display: grid;
-  grid-gap: 2rem;
-  grid-template-columns: repeat(auto-fit, minmax(24rem, 1fr));
-}
-
-.edition_module {
-  font-size: var(--subtitle-size);
-  width: 100% !important;
-
-  &:not(.collapsed) {
-    &::after {
-      background-color: var(--primary-grey);
-    }
-  }
-  &:hover::after {
-    background-color: var(--primary-blue);
-  }
-  &.hover-inside {
-    background-color: var(--primary-light) !important;
-    &::after {
-      transform: scaleY(1) !important;
-    }
-    &:not(.collapsed)::after {
-      background-color: var(--primary-grey) !important;
-    }
-  }
-
-  &_main {
-    align-items: center;
-    display: flex;
-    justify-content: space-between;
-    padding: 0 1em;
-    width: 100%;
-    button {
-      font-size: var(--subtitle-size);
-      transition: transform 100ms ease-in-out;
-    }
-  }
-  &:not(.collapsed) > &_main > &_buttons > button {
-    transform: rotate(180deg);
-  }
-
-  &_buttons {
-    display: flex;
-    align-items: center;
-    justify-content: center;
-  }
-
-  &_group {
-    background-color: var(--primary-blue);
-    color: var(--primary-light);
-    font-size: var(--table-size);
-    max-height: 2em;
-    margin-right: 1.5rem;
-    padding: .3em .8em;
-  }
-
-  &_assignments {
-    max-height: 0;
-    transform: scaleY(0);
-    transform-origin: top;
-  }
-  &:not(.collapsed) + &_assignments {
-    max-height: initial;
-    transform: scaleY(1);
-    transition: transform 100ms ease-in-out;
-  }
-
-  &_assignment {
-    background-color: var(--primary-light);
-    color: var(--primary-blue);
-    cursor: pointer;
-    display: block;
-    padding: 2rem;
-    position: relative;
-    text-decoration: none;
-
-    &::after {
-      background-color: var(--primary-grey);
-      bottom: 0;
-      content: '';
-      height: 2px;
-      left: 0;
-      position: absolute;
-      transform-origin: bottom;
-      width: 100%;
-    }
-    &:last-child::after {
-      background-color: var(--primary-blue);
-    }
-    &:hover {
-      background-color: var(--primary-grey);
-      &::after {
-        background-color: var(--primary-blue);
-        transform: scaleY(2);
-      }
-    }
-  }
-}
\ No newline at end of file
diff --git a/src/main/resources/scss/editions.scss b/src/main/resources/scss/editions.scss
deleted file mode 100644
index 1cb07b34ea75a0504f5cb87be085c41eb6d5fc55..0000000000000000000000000000000000000000
--- a/src/main/resources/scss/editions.scss
+++ /dev/null
@@ -1,42 +0,0 @@
-@import 'vars';
-
-.editions {
-  display: grid;
-  grid-gap: 2em;
-  grid-template-columns: repeat(auto-fill, minmax(20em, 1fr));
-}
-
-.edition {
-  padding: 2.2em 1em;
-  width: 100%;
-
-  flex-direction: row !important;
-  justify-content: space-between;
-
-  &_info {
-    display: flex;
-    flex-direction: column;
-  }
-
-  &_name {
-    color: var(--primary-blue);
-    font-size: var(--subtitle-size);
-  }
-
-  &_course {
-    color: var(--input-grey);
-  }
-
-  &_role {
-    background-color: var(--primary-blue);
-    color: var(--primary-light);
-
-    max-height: 2em;
-    padding: .5em 1em;
-    white-space: nowrap;
-  }
-
-  &_new {
-    width: 100%;
-  }
-}
\ No newline at end of file
diff --git a/src/main/resources/scss/main.scss b/src/main/resources/scss/main.scss
deleted file mode 100644
index d4bf40dbda15a44fc67eace30fd82495a5773c5f..0000000000000000000000000000000000000000
--- a/src/main/resources/scss/main.scss
+++ /dev/null
@@ -1,92 +0,0 @@
-@import 'vars';
-
-* {
-  box-sizing: border-box;
-  margin: 0;
-  padding: 0;
-}
-
-html, body, .page {
-  height: 100%;
-}
-
-button {
-  font-size: var(--table-size);
-}
-.smaller {
-  font-size: var(--regular-size);
-}
-
-.page {
-  display: flex;
-  flex-direction: column;
-  overflow-y: auto;
-  width: 100%;
-}
-
-.main-section {
-  display: flex;
-  flex-grow: 1;
-  margin: 0 auto;
-  max-width: var(--body-width);
-  width: 100%;
-  @media(max-width: 768px) {
-    flex-direction: column;
-  }
-}
-
-.content {
-  margin: 2rem 0;
-  width: 100%;
-}
-
-.breadcrumbs {
-  $margin: 2em;
-
-  margin: $margin auto;
-  max-width: var(--body-width);
-  position: relative;
-  width: 100%;
-
-  > a, > span {
-    color: var(--input-grey);
-    font-size: var(--regular-size);
-    text-decoration: none;
-  }
-
-  > a:hover {
-    color: var(--primary-dark);
-    text-decoration: none;
-  }
-
-  &::after {
-    background-color: var(--primary-grey);
-    bottom: -$margin;
-    content: '';
-    height: 1px;
-    left: 0;
-    position: absolute;
-    width: 100%;
-  }
-}
-
-.title {
-  font-family: var(--title-font);
-  font-size: var(--title-size);
-  font-weight: 100;
-}
-.subtitle {
-  font-size: var(--subtitle-size);
-  font-weight: normal;
-}
-
-.hidden {
-  display: none !important;
-}
-
-@import 'header';
-@import 'footer';
-@import 'sidebar';
-@import 'elements';
-
-@import 'utility';
diff --git a/src/main/resources/scss/progress.scss b/src/main/resources/scss/progress.scss
deleted file mode 100644
index aae550c5947f388303b4640f71db350bd7929b61..0000000000000000000000000000000000000000
--- a/src/main/resources/scss/progress.scss
+++ /dev/null
@@ -1,46 +0,0 @@
-.progress.table {
-
-  th, td {
-    border: 1px solid var(--primary-grey);
-
-    &.pass {
-      background-color: var(--secondary-cyan);
-      color: var(--primary-light);
-    }
-    &.fail {
-      background-color: var(--primary-red);
-      color: var(--primary-light);
-    }
-    &.submitted {
-      background-color: var(--primary-blue);
-      color: var(--primary-light);
-    }
-
-    &:first-child {
-      border-left: none;
-      border-right: 4px double var(--primary-grey);
-    }
-    &:not(:first-child) {
-      text-align: center;
-    }
-  }
-
-  .table_header {
-    th, td {
-      border-bottom: 3px solid var(--primary-dark);
-    }
-    td {
-      color: var(--input-grey);
-    }
-  }
-
-  .table_info {
-    color: var(--input-grey);
-    font-size: 0.8rem;
-  }
-
-}
-
-.tabs + .progress.table {
-  margin-top: 2rem;
-}
\ No newline at end of file
diff --git a/src/main/resources/static/img/tudelft-logo.png b/src/main/resources/static/img/tudelft_logo.png
similarity index 100%
rename from src/main/resources/static/img/tudelft-logo.png
rename to src/main/resources/static/img/tudelft_logo.png
diff --git a/src/main/resources/static/img/tudelft_logo_dark.png b/src/main/resources/static/img/tudelft_logo_dark.png
new file mode 100644
index 0000000000000000000000000000000000000000..84bdaca27b2d70126c98d298ea7f80ab8ce8587a
Binary files /dev/null and b/src/main/resources/static/img/tudelft_logo_dark.png differ
diff --git a/src/main/resources/static/js/announcements.js b/src/main/resources/static/js/announcements.js
index 9dc59031604c9edd046739743e1b15fac6f4af3d..e190ce4b1907ef801c15852a55262cb5d3db222e 100644
--- a/src/main/resources/static/js/announcements.js
+++ b/src/main/resources/static/js/announcements.js
@@ -36,4 +36,4 @@ function dismissAnnouncement(id) {
     let dismissedAnnouncements = JSON.parse(Cookies.get("dismissed-announcements"));
     dismissedAnnouncements.push(id);
     Cookies.set("dismissed-announcements", JSON.stringify(dismissedAnnouncements), { path: "/" });
-}
\ No newline at end of file
+}
diff --git a/src/main/resources/static/js/notifications.js b/src/main/resources/static/js/notifications.js
index ce1a0b681835eafcbb7c3808c9531daa460d0613..52872b5409e38acb6e695cba9c7f4751cb210283 100644
--- a/src/main/resources/static/js/notifications.js
+++ b/src/main/resources/static/js/notifications.js
@@ -24,7 +24,7 @@ let username;
 let socket;
 let stompClient;
 
-window.addEventListener('DOMContentLoaded', () => {
+window.addEventListener("DOMContentLoaded", () => {
     askNotificationPermission();
     connectToSocket();
 });
@@ -33,8 +33,8 @@ window.addEventListener('DOMContentLoaded', () => {
  * Keeps track of the notifications to clear them.
  */
 let notificationList = [];
-document.addEventListener('visibilitychange', function() {
-    if (document.visibilityState === 'visible') {
+document.addEventListener("visibilitychange", function () {
+    if (document.visibilityState === "visible") {
         for (let notif of notificationList) {
             notif.close();
         }
@@ -45,22 +45,32 @@ document.addEventListener('visibilitychange', function() {
  * Connects to the websocket.
  */
 function connectToSocket() {
-    if (window.location.pathname === "/" || window.location.pathname === "/privacy"
-        || window.location.pathname === "/auth/login") return;
-
-    socket = new SockJS('/socket-endpoint');
+    if (
+        window.location.pathname === "/" ||
+        window.location.pathname === "/privacy" ||
+        window.location.pathname === "/auth/login"
+    )
+        return;
+
+    socket = new SockJS("/socket-endpoint");
     stompClient = Stomp.over(socket);
 
     const token = $("meta[name='_csrf']").attr("content");
     const header = $("meta[name='_csrf_header']").attr("content");
 
-    if ("Notification" in window && "permission" in Notification && Notification.permission === "granted") {
+    if (
+        "Notification" in window &&
+        "permission" in Notification &&
+        Notification.permission === "granted"
+    ) {
         let headers = {};
         headers[header] = token;
 
         stompClient.connect(headers, function (frame) {
             username = frame.headers["user-name"];
-            stompClient.subscribe("/user/" + username + "/notifications", msg => receiveNotification(JSON.parse(msg.body)));
+            stompClient.subscribe("/user/" + username + "/notifications", msg =>
+                receiveNotification(JSON.parse(msg.body))
+            );
             retrieveNotifications();
         });
     }
@@ -71,7 +81,7 @@ function connectToSocket() {
  */
 function askNotificationPermission() {
     function handlePermission(permission) {
-        if(!("permission" in Notification)) {
+        if (!("permission" in Notification)) {
             Notification.permission = permission;
             connectToSocket();
         }
@@ -80,14 +90,14 @@ function askNotificationPermission() {
     function checkNotificationPromise() {
         try {
             Notification.requestPermission().then();
-        } catch(e) {
+        } catch (e) {
             return false;
         }
         return true;
     }
 
     if ("Notification" in window) {
-        if(checkNotificationPromise()) {
+        if (checkNotificationPromise()) {
             Notification.requestPermission().then(handlePermission);
         } else {
             Notification.requestPermission(handlePermission);
@@ -109,7 +119,7 @@ function receiveNotification(notifObject) {
     }
 }
 
-let amount = 0
+let amount = 0;
 /**
  * Adds a notification to the list of notifications.
  *
@@ -139,4 +149,4 @@ function retrieveNotifications() {
             res.forEach(addNotification);
         });
     }
-}
\ No newline at end of file
+}
diff --git a/src/main/resources/table-setup.yaml b/src/main/resources/table-setup.yaml
index cf011b5e3a496fb69bb675cd37f66d69e6194780..d3f25793429de271eba398cc0ef889964555d1d2 100644
--- a/src/main/resources/table-setup.yaml
+++ b/src/main/resources/table-setup.yaml
@@ -619,7 +619,7 @@ databaseChangeLog:
                     nullable: false
                   defaultValueBoolean: true
                   name: hidden
-                  type: BIT
+                  type: BOOLEAN
             tableName: submit_edition
   - changeSet:
       id: 1657698973831-22
diff --git a/src/main/resources/templates/announcement/add.html b/src/main/resources/templates/announcement/add.html
index 65f7eac1ed839d9f3a7d190bdee72607b17a10d0..adaa6e05376a2bba449bb42c58b4f17be54c17c3 100644
--- a/src/main/resources/templates/announcement/add.html
+++ b/src/main/resources/templates/announcement/add.html
@@ -18,49 +18,104 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <dialog th:fragment="overlay" id="add-announcement-overlay" class="dialog">
+            <form
+                th:object="${announcement}"
+                th:action="@{|/announcement/${edition == null ? 'global' : 'edition'}|}"
+                method="post"
+                class="flex vertical p-7">
+                <h2 class="underlined font-500" th:text="#{announcement.add}"></h2>
 
-<div th:fragment="overlay" id="add-announcement-overlay" class="hidden overlay">
-    <form th:object="${announcement}" th:action="@{|/announcement/${edition == null ? 'global' : 'edition'}|}" method="post" class="boxed-content">
-        <h1 class="underlined title" th:text="#{announcement.add}"></h1>
-        <div class="form-group">
-            <input th:if="${edition != null}" type="hidden" th:name="editionId" th:value="${edition.id}"/>
+                <div class="grid col-2" style="--col-1: minmax(0, 10rem)">
+                    <input
+                        th:if="${edition != null}"
+                        type="hidden"
+                        th:name="editionId"
+                        th:value="${edition.id}" />
 
-            <label for="message" th:text="#{announcement.message}"></label>
-            <input id="message" th:name="message" th:placeholder="#{announcement.message.enter}" type="text" class="textfield" required/>
+                    <label for="message" th:text="#{announcement.message}"></label>
+                    <input
+                        id="message"
+                        th:name="message"
+                        th:placeholder="#{announcement.message.enter}"
+                        type="text"
+                        class="textfield"
+                        required />
 
-            <label for="bg-color" th:text="#{announcement.background_colour}"></label>
-            <input id="bg-color" th:name="backgroundColour" value="#FFFFFF" type="color" required/>
+                    <label for="bg-color" th:text="#{announcement.background_colour}"></label>
+                    <input
+                        id="bg-color"
+                        th:name="backgroundColour"
+                        value="#FFFFFF"
+                        type="color"
+                        required />
 
-            <label for="text-color" th:text="#{announcement.text_colour}"></label>
-            <input id="text-color" th:name="textColour" value="#000000" type="color" required/>
+                    <label for="text-color" th:text="#{announcement.text_colour}"></label>
+                    <input
+                        id="text-color"
+                        th:name="textColour"
+                        value="#000000"
+                        type="color"
+                        required />
 
-            <label for="start-date" th:text="#{announcement.start_time}"></label>
-            <div class="form-hstack">
-                <input id="start-date" th:value="${#temporals.createToday()}" style="flex: 1;" th:name="startTimeDate" type="date" class="textfield" required/>
-                <input id="start-time" value="00:00" style="margin-left: .5rem;" th:name="startTimeTime" type="time" class="textfield" required/>
-            </div>
+                    <label for="start-date" th:text="#{announcement.start_time}"></label>
+                    <div class="grid col-2 gap-3" style="--col-2: 5rem">
+                        <input
+                            id="start-date"
+                            th:value="${#temporals.createToday()}"
+                            th:name="startTimeDate"
+                            type="date"
+                            class="textfield"
+                            required />
+                        <input
+                            id="start-time"
+                            value="00:00"
+                            th:name="startTimeTime"
+                            type="time"
+                            class="textfield"
+                            required />
+                    </div>
 
-            <label for="end-date" th:text="#{announcement.end_time}"></label>
-            <div class="form-hstack">
-                <input id="end-date" style="flex: 1;" th:name="endTimeDate" type="date" class="textfield" required/>
-                <input id="end-time" style="margin-left: .5rem;" th:name="endTimeTime" type="time" class="textfield" required/>
-            </div>
+                    <label for="end-date" th:text="#{announcement.end_time}"></label>
+                    <div class="grid col-2 gap-3" style="--col-2: 5rem">
+                        <input
+                            id="end-date"
+                            th:name="endTimeDate"
+                            type="date"
+                            class="textfield"
+                            required />
+                        <input
+                            id="end-time"
+                            th:name="endTimeTime"
+                            type="time"
+                            class="textfield"
+                            required />
+                    </div>
 
-            <label for="dismissible" th:text="#{announcement.dismissible}"></label>
-            <div class="checkbox">
-                <input id="dismissible" th:name="isDismissible" type="checkbox"/>
-                <label for="dismissible" th:text="#{announcement.dismissible.enter}"></label>
-            </div>
+                    <label for="dismissible" th:text="#{announcement.dismissible}"></label>
+                    <div class="checkbox">
+                        <input id="dismissible" th:name="isDismissible" type="checkbox" />
+                        <label
+                            for="dismissible"
+                            th:text="#{announcement.dismissible.enter}"></label>
+                    </div>
+                </div>
 
-        </div>
-        <div class="form-buttons">
-            <button type=button class="warning text-button" th:text="#{general.cancel}" onclick="toggleOverlay('add-announcement-overlay')"></button>
-            <button type=submit class="text-button" th:text="#{general.save}"></button>
-        </div>
-    </form>
-</div>
-
-</body>
-</html>
\ No newline at end of file
+                <div class="flex space-between">
+                    <button
+                        type="button"
+                        class="button p-less"
+                        data-style="outlined"
+                        th:text="#{general.cancel}"
+                        data-cancel></button>
+                    <button type="submit" class="button p-less" th:text="#{general.save}"></button>
+                </div>
+            </form>
+        </dialog>
+    </body>
+</html>
diff --git a/src/main/resources/templates/announcement/edition.html b/src/main/resources/templates/announcement/edition.html
index 68a0ac10b3c821e104247d16afd39376c61bc5cc..ac0d553320cc9e4dea0214d591df817fdb526c86 100644
--- a/src/main/resources/templates/announcement/edition.html
+++ b/src/main/resources/templates/announcement/edition.html
@@ -18,22 +18,27 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
-
-<div th:fragment="top" th:each="announcement : ${@announcementService.getActiveEditionAnnouncements(edition.id)}"
-     th:id="|edition-${announcement.id}|" class="in_content announcement">
-    <div class="announcement_accent" th:style="|background-color: #${announcement.backgroundHex};|"></div>
-    <div class="announcement_text">
-        <button th:if=${announcement.isDismissible} class="text-button" style="float: right;"
-                th:onclick="|dismissAnnouncement('edition-${announcement.id}')|"><span class="fas fa-times"></span></button>
-        <p th:style="|color: #${announcement.textHex};|" th:text="${announcement.message}"></p>
-    </div>
-
-    <script>
-        removeDismissed();
-    </script>
-</div>
-
-</body>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <div
+            layout:fragment="top"
+            th:each="announcement : ${@announcementService.getActiveEditionAnnouncements(edition.id)}"
+            th:id="|edition-${announcement.id}|"
+            class="banner mb-3"
+            th:style="|--color: #${announcement.backgroundHex};|">
+            <p th:style="|color: #${announcement.textHex};|" th:text="${announcement.message}"></p>
+            <button
+                th:if="${announcement.isDismissible}"
+                class="fa-solid fa-times"
+                style="margin-left: auto; background: none; border: none; cursor: pointer"
+                th:onclick="|dismissAnnouncement('edition-${announcement.id}')|"></button>
+
+            <script>
+                removeDismissed();
+            </script>
+        </div>
+    </body>
 </html>
diff --git a/src/main/resources/templates/announcement/end.html b/src/main/resources/templates/announcement/end.html
index 441d79cc3d71978da7c03e3ee4f4f5e1d93b6ee1..148156c7be79478950fb376966137eeac8d0d5b1 100644
--- a/src/main/resources/templates/announcement/end.html
+++ b/src/main/resources/templates/announcement/end.html
@@ -18,19 +18,32 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
-
-<div th:fragment="overlay" th:id="|end-${announcement.id}-overlay|" class="hidden confirm overlay">
-    <form th:action="@{|/announcement/${edition == null ? 'global' : 'edition'}/${announcement.id}/end|}" method="post" class="boxed-content">
-        <p class="confirm_text" th:text="#{announcement.end.confirm}"></p>
-        <div class="form-buttons">
-            <button type=button class="warning text-button" th:text="#{general.cancel}"
-                    th:onclick="|toggleOverlay('end-${announcement.id}-overlay')|"></button>
-            <button type=submit class="text-button" th:text="#{general.remove}"></button>
-        </div>
-    </form>
-</div>
-
-</body>
-</html>
\ No newline at end of file
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <dialog th:fragment="overlay" th:id="|end-${announcement.id}-overlay|" class="dialog">
+            <form
+                th:action="@{|/announcement/${edition == null ? 'global' : 'edition'}/${announcement.id}/end|}"
+                method="post"
+                class="flex vertical p-7">
+                <h2 class="underlined font-500">End announcement</h2>
+                <p class="confirm_text" th:text="#{announcement.end.confirm}"></p>
+                <div class="flex space-between">
+                    <button
+                        type="button"
+                        class="button p-less"
+                        data-style="outlined"
+                        th:text="#{general.cancel}"
+                        data-cancel></button>
+                    <button
+                        type="submit"
+                        class="button p-less"
+                        data-type="error"
+                        th:text="#{general.remove}"></button>
+                </div>
+            </form>
+        </dialog>
+    </body>
+</html>
diff --git a/src/main/resources/templates/announcement/list.html b/src/main/resources/templates/announcement/list.html
index 5c7e6c7f337869169568561e536704707000a6ee..861e132e1e7c3310dd2b2512cfcfe723cd010b4f 100644
--- a/src/main/resources/templates/announcement/list.html
+++ b/src/main/resources/templates/announcement/list.html
@@ -18,81 +18,79 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
-      layout:decorate="~{container}">
-<head>
-    <title th:text="#{announcement.announcements}"></title>
-</head>
-
-<body>
-
-<div layout:fragment="breadcrumbs" th:if="${edition == null}" class="hidden"></div>
-
-<div layout:fragment="breadcrumbs" th:unless="${edition == null}">
-    <a th:href="@{/edition/enrolled}" th:text="#{edition.editions}"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{|/edition/${edition.id}|}" th:text="|${edition.course.name} - ${edition.name}|"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{|/edition/${edition.id}/announcements|}" th:text="#{announcement.announcements}"></a>
-</div>
-
-<div layout:fragment="sidebar" th:if="${edition == null}" class="hidden"></div>
-
-<div layout:fragment="sidebar" th:unless="${edition == null}">
-    <a class="sidebar_item" th:href="@{|/edition/${edition.id}|}" th:text="#{general.details}"></a>
-    <a th:if="${@authorizationService.canEditAnnouncementsForEdition(edition.id)}" class="sidebar_item" th:href="@{|/edition/${edition.id}/announcements|}" th:text="#{announcement.announcements}"></a>
-    <a th:if="${@authorizationService.canViewEditionOverview(edition.id)}" class="sidebar_item" th:href="@{|/edition/${edition.id}/students|}" th:text="#{edition.students}"></a>
-    <a th:if="${not @authorizationService.canViewEditionOverview(edition.id) and @authorizationService.canViewEditionStudents(edition.id)}" class="sidebar_item" th:href="@{|/edition/${edition.id}/students/actions|}" th:text="#{edition.students}"></a>
-    <a th:if="${@authorizationService.canViewEditionStaff(edition.id)}" class="sidebar_item" th:href="@{|/edition/${edition.id}/staff|}" th:text="#{edition.staff}"></a>
-    <a th:if="${@authorizationService.canViewEditionStatistics(edition.id)}" class="sidebar_item" th:href="@{|/edition/${edition.id}/statistics|}" th:text="#{general.statistics}"></a>
-</div>
-
-<div layout:fragment="content">
-
-    <div th:if="${edition != null}" th:remove="tag">
-        <div th:replace="~{announcement/edition :: top}"></div>
-    </div>
-
-    <h1 class="title" th:text="${edition == null} ? #{announcement.global_announcements} : #{announcement.announcements}"></h1>
-
-    <table class="table">
-        <tr class="table_header">
-            <th th:text="#{announcement.message}"></th>
-            <th th:text="#{announcement.start_time}"></th>
-            <th th:text="#{announcement.end_time}"></th>
-            <th th:text="#{announcement.dismissible}"></th>
-            <th><div class="table_actions">
-                <button class="text-button" th:text="#{announcement.add}" onclick="toggleOverlay('add-announcement-overlay')"></button>
-            </div></th>
-        </tr>
-        <tr th:if="${announcements.isEmpty()}">
-            <td th:text="#{announcement.no_announcements}"></td>
-            <td></td>
-            <td></td>
-            <td></td>
-            <td></td>
-        </tr>
-        <tr th:with="now = ${#temporals.createNow()}" th:each="announcement : ${announcements}"
-            th:style="|background-color: #${announcement.backgroundHex};
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+    layout:decorate="~{container}">
+    <head>
+        <title th:text="#{announcement.announcements}"></title>
+    </head>
+
+    <body>
+        <main layout:fragment="content">
+            <th:block th:if="${edition != null}">
+                <th:block layout:replace="~{edition/top :: top}"></th:block>
+            </th:block>
+
+            <h1
+                th:if="${edition == null}"
+                class="font-800 mb-5"
+                th:text="#{announcement.global_announcements}"></h1>
+
+            <div class="grid">
+                <table class="table">
+                    <tr class="table__header">
+                        <th th:text="#{announcement.message}"></th>
+                        <th th:text="#{announcement.start_time}"></th>
+                        <th th:text="#{announcement.end_time}"></th>
+                        <th th:text="#{announcement.dismissible}"></th>
+                        <th>
+                            <div class="flex justify-end">
+                                <button
+                                    class="button p-min"
+                                    data-style="outlined"
+                                    th:text="#{announcement.add}"
+                                    data-dialog="add-announcement-overlay"></button>
+                            </div>
+                        </th>
+                    </tr>
+                    <tr th:if="${announcements.isEmpty()}">
+                        <td th:text="#{announcement.no_announcements}"></td>
+                        <td></td>
+                        <td></td>
+                        <td></td>
+                        <td></td>
+                    </tr>
+                    <tr
+                        th:with="now = ${#temporals.createNow()}"
+                        th:each="announcement : ${announcements}"
+                        th:style="|background-color: #${announcement.backgroundHex}44;
                        color: #${announcement.textHex};|">
-            <td style="max-width: 20rem;" th:text="${announcement.message}"></td>
-            <td th:text="${#temporals.format(announcement.startTime, 'dd-MM-yyyy HH:mm')}"></td>
-            <td th:text="${#temporals.format(announcement.endTime, 'dd-MM-yyyy HH:mm')}"></td>
-            <td th:text="${announcement.isDismissible} ? #{announcement.dismissible} : #{announcement.not_dismissible}"></td>
-            <td class="table_actions">
-                <button class="warning text-button" th:text="#{announcement.end}"
-                        th:disabled="${now > announcement.endTime}"
-                        th:onclick="|toggleOverlay('end-${announcement.id}-overlay')|"></button>
-            </td>
-        </tr>
-    </table>
-
-    <div th:replace="~{announcement/add :: overlay}"></div>
-    <div th:each="announcement : ${announcements}">
-        <div th:replace="~{announcement/end :: overlay}"></div>
-    </div>
-
-</div>
-
-</body>
-</html>
\ No newline at end of file
+                        <td style="max-width: 20rem" th:text="${announcement.message}"></td>
+                        <td
+                            th:text="${#temporals.format(announcement.startTime, 'dd-MM-yyyy HH:mm')}"></td>
+                        <td
+                            th:text="${#temporals.format(announcement.endTime, 'dd-MM-yyyy HH:mm')}"></td>
+                        <td
+                            th:text="${announcement.isDismissible} ? #{announcement.dismissible} : #{announcement.not_dismissible}"></td>
+                        <td class="flex justify-end">
+                            <button
+                                class="button p-min"
+                                data-style="outlined"
+                                data-type="error"
+                                th:text="#{announcement.end}"
+                                th:disabled="${now > announcement.endTime}"
+                                th:data-dialog="|end-${announcement.id}-overlay|"></button>
+                        </td>
+                    </tr>
+                </table>
+            </div>
+
+            <div th:replace="~{announcement/add :: overlay}"></div>
+            <div th:each="announcement : ${announcements}">
+                <div th:replace="~{announcement/end :: overlay}"></div>
+            </div>
+        </main>
+    </body>
+</html>
diff --git a/src/main/resources/templates/announcement/top.html b/src/main/resources/templates/announcement/top.html
index 0f1a6e7c3315ae23bfaf7a21947221b14f260003..ba4403e2576877d8b8ea2558426c7c718b0f8d5f 100644
--- a/src/main/resources/templates/announcement/top.html
+++ b/src/main/resources/templates/announcement/top.html
@@ -17,16 +17,31 @@
     along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 -->
-<div th:if="${@authorizationService.isAuthenticated()}" th:each="announcement : ${@announcementService.getActiveGlobalAnnouncements()}"
-     th:id="|global-${announcement.id}|" class="announcement">
-    <div class="announcement_accent" th:style="|background-color: #${announcement.backgroundHex};|"></div>
-    <div class="announcement_text">
-        <button th:if=${announcement.isDismissible} class="text-button" style="float: right;"
-                th:onclick="|dismissAnnouncement('global-${announcement.id}')|"><span class="fas fa-times"></span></button>
-        <p th:style="|color: #${announcement.textHex};|" th:text="${announcement.message}"></p>
-    </div>
-</div>
+<!DOCTYPE html>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <div th:if="${@authorizationService.isAuthenticated()}" layout:fragment="top">
+            <div
+                class="banner mb-3"
+                th:each="announcement : ${@announcementService.getActiveGlobalAnnouncements()}"
+                th:id="|global-${announcement.id}|"
+                th:style="|--color: #${announcement.backgroundHex};|">
+                <p
+                    th:style="|color: #${announcement.textHex};|"
+                    th:text="${announcement.message}"></p>
+                <button
+                    th:if="${announcement.isDismissible}"
+                    class="fa-solid fa-times"
+                    style="margin-left: auto; background: none; border: none; cursor: pointer"
+                    th:onclick="|dismissAnnouncement('global-${announcement.id}')|"></button>
+            </div>
 
-<script>
-    removeDismissed();
-</script>
+            <script>
+                removeDismissed();
+            </script>
+        </div>
+    </body>
+</html>
diff --git a/src/main/resources/templates/assignment/add.html b/src/main/resources/templates/assignment/add.html
index 636f8e45a1800537f3d760e04d83b988599e6fd7..a725f1e70cde4ca4864610071e386d0a9d2adc49 100644
--- a/src/main/resources/templates/assignment/add.html
+++ b/src/main/resources/templates/assignment/add.html
@@ -18,88 +18,167 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
-
-<div th:fragment="overlay" th:if="${@authorizationService.canCreateAssignment(module.id)}" id="add-assignment-overlay" class="hidden overlay">
-    <form th:object="${assignment}" th:action="@{/assignment}" method="post" class="boxed-content">
-        <h1 class="underlined title" th:text="#{assignment.add}"></h1>
-        <div class="form-group">
-            <input type="hidden" th:name="module.id" th:value="${module.id}"/>
-
-            <label for="assignment-name" th:text="#{general.name}"></label>
-            <input id="assignment-name" th:name="name" th:placeholder="#{general.name.enter}" type="text" class="textfield" required/>
-
-            <label for="assignment-description" th:text="#{general.description}"></label>
-            <textarea id="assignment-description" th:name="description.text" class="textarea" th:placeholder="#{general.description.enter}"></textarea>
-
-            <label for="assignment-deadline" th:text="#{assignment.deadline}"></label>
-            <input id="assignment-deadline" th:name="deadline" th:placeholder="#{assignment.deadline.enter}" type="text" class="textfield"
-                   pattern="|[0-3]\d-[0-1]\d-\d\d\d\d [0-2]\d:[0-5]\d">
-
-            <div class="form-separator"></div>
-
-            <label for="assignment-script" th:text="#{assignment.script}"></label>
-            <input id="assignment-script" th:name="approveScript" th:placeholder="#{assignment.script.enter}" type="url" class="textfield">
-
-            <label for="assignment-score-type" class="center-label" th:text="#{grading.score_type}"></label>
-            <select class="selectbox" id="assignment-score-type" th:field="*{scoreType}">
-                <option th:each="type : ${T(nl.tudelft.labracore.lib.api.GradeScheme).values()}"
-                        th:value="${type}"
-                        th:text="#{|grading.type.${#strings.toLowerCase(type.name())}|}"></option>
-            </select>
-
-            <div class="form-separator"></div>
-
-            <div>
-                <label for="assignment-leniency" th:text="#{assignment.leniency}"></label>
-                <div class="tooltip">
-                    <span class="tooltip_icon">?</span>
-                    <p class="tooltip_text" th:text="#{assignment.leniency.help}"></p>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <dialog
+            th:fragment="overlay"
+            th:if="${@authorizationService.canCreateAssignment(module.id)}"
+            id="add-assignment-overlay"
+            class="dialog">
+            <form
+                th:object="${assignment}"
+                th:action="@{/assignment}"
+                method="post"
+                class="flex vertical p-7">
+                <h1 class="underlined font-500" th:text="#{assignment.add}"></h1>
+
+                <div class="grid col-2" style="--col-1: minmax(0, 14rem)">
+                    <input type="hidden" th:name="module.id" th:value="${module.id}" />
+
+                    <label for="assignment-name" th:text="#{general.name}"></label>
+                    <input
+                        id="assignment-name"
+                        th:name="name"
+                        th:placeholder="#{general.name.enter}"
+                        type="text"
+                        class="textfield"
+                        required />
+
+                    <label for="assignment-description" th:text="#{general.description}"></label>
+                    <textarea
+                        id="assignment-description"
+                        th:name="description.text"
+                        class="textfield"
+                        th:placeholder="#{general.description.enter}"></textarea>
+
+                    <label for="assignment-deadline" th:text="#{assignment.deadline}"></label>
+                    <input
+                        id="assignment-deadline"
+                        th:name="deadline"
+                        th:placeholder="#{assignment.deadline.enter}"
+                        type="text"
+                        class="textfield"
+                        pattern="|[0-3]\d-[0-1]\d-\d\d\d\d [0-2]\d:[0-5]\d" />
+
+                    <div class="underlined" style="grid-column: span 2"></div>
+
+                    <label for="assignment-script" th:text="#{assignment.script}"></label>
+                    <input
+                        id="assignment-script"
+                        th:name="approveScript"
+                        th:placeholder="#{assignment.script.enter}"
+                        type="url"
+                        class="textfield" />
+
+                    <label
+                        for="assignment-score-type"
+                        class="center-label"
+                        th:text="#{grading.score_type}"></label>
+                    <select class="textfield" id="assignment-score-type" th:field="*{scoreType}">
+                        <option
+                            th:each="type : ${T(nl.tudelft.labracore.lib.api.GradeScheme).values()}"
+                            th:value="${type}"
+                            th:text="#{|grading.type.${#strings.toLowerCase(type.name())}|}"></option>
+                    </select>
+
+                    <div class="underlined" style="grid-column: span 2"></div>
+
+                    <div>
+                        <label for="assignment-leniency" th:text="#{assignment.leniency}"></label>
+                        <div class="tooltip">
+                            <span class="tooltip__control fa-solid fa-question"></span>
+                            <div role="tooltip" style="white-space: initial; min-width: 20rem">
+                                <p th:text="#{assignment.leniency.help}"></p>
+                            </div>
+                        </div>
+                    </div>
+                    <input
+                        id="assignment-leniency"
+                        th:name="lateSubmissionLeniency"
+                        type="number"
+                        min="0"
+                        class="textfield" />
+
+                    <div>
+                        <label
+                            for="assignment-accept-late"
+                            th:text="#{assignment.accept_late}"></label>
+                        <div class="tooltip">
+                            <span class="tooltip__control fa-solid fa-question"></span>
+                            <div role="tooltip" style="white-space: initial; min-width: 20rem">
+                                <p th:text="#{assignment.accept_late.help}"></p>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="checkbox">
+                        <input
+                            id="assignment-accept-late"
+                            th:name="acceptLateSubmissions"
+                            type="checkbox" />
+                        <label for="assignment-accept-late" th:text="#{general.allow}"></label>
+                    </div>
+
+                    <div class="underlined" style="grid-column: span 2"></div>
+
+                    <div>
+                        <label
+                            for="assignment-submission-limit"
+                            th:text="#{assignment.submission_limit}"></label>
+                        <div class="tooltip">
+                            <span class="tooltip__control fa-solid fa-question"></span>
+                            <div role="tooltip" style="white-space: initial; min-width: 20rem">
+                                <p th:text="#{assignment.submission_limit.help}"></p>
+                            </div>
+                        </div>
+                    </div>
+                    <input
+                        id="assignment-submission-limit"
+                        th:name="submissionLimit"
+                        type="number"
+                        min="0"
+                        class="textfield"
+                        th:placeholder="#{assignment.submission_limit.enter}" />
+
+                    <div>
+                        <label
+                            for="assignment-allowed-files"
+                            th:text="#{assignment.allowed_files}"></label>
+                        <div class="tooltip">
+                            <span class="tooltip__control fa-solid fa-question"></span>
+                            <div
+                                role="tooltip"
+                                style="
+                                    white-space: initial;
+                                    min-width: 20rem;
+                                    top: initial;
+                                    bottom: 1.2rem;
+                                    transform-origin: bottom left;
+                                ">
+                                <p th:text="#{assignment.allowed_files.help}"></p>
+                            </div>
+                        </div>
+                    </div>
+                    <input
+                        id="assignment-allowed-files"
+                        th:name="allowedFileTypes"
+                        type="text"
+                        class="textfield"
+                        th:placeholder="#{assignment.allowed_files.enter}" />
                 </div>
-            </div>
-            <input id="assignment-leniency" th:name="lateSubmissionLeniency" type="number" min="0" class="textfield"/>
-
-            <div>
-                <label for="assignment-accept-late" th:text="#{assignment.accept_late}"></label>
-                <div class="tooltip">
-                    <span class="tooltip_icon">?</span>
-                    <p class="tooltip_text" th:text="#{assignment.accept_late.help}"></p>
-                </div>
-            </div>
-            <div class="checkbox">
-                <input id="assignment-accept-late" th:name="acceptLateSubmissions" type="checkbox"/>
-                <label for="assignment-accept-late" th:text="#{general.allow}"></label>
-            </div>
-
-            <div class="form-separator"></div>
-
-            <div>
-                <label for="assignment-submission-limit" th:text="#{assignment.submission_limit}"></label>
-                <div class="tooltip">
-                    <span class="tooltip_icon">?</span>
-                    <p class="tooltip_text" th:text="#{assignment.submission_limit.help}"></p>
-                </div>
-            </div>
-            <input id="assignment-submission-limit" th:name="submissionLimit" type="number" min="0" class="textfield"
-                   th:placeholder="#{assignment.submission_limit.enter}"/>
-
-            <div>
-                <label for="assignment-allowed-files" th:text="#{assignment.allowed_files}"></label>
-                <div class="tooltip">
-                    <span class="tooltip_icon">?</span>
-                    <p class="tooltip_text" th:text="#{assignment.allowed_files.help}"></p>
+
+                <div class="flex space-between">
+                    <button
+                        type="button"
+                        class="button p-less"
+                        data-style="outlined"
+                        th:text="#{general.cancel}"
+                        data-cancel></button>
+                    <button type="submit" class="button p-less" th:text="#{general.save}"></button>
                 </div>
-            </div>
-            <input id="assignment-allowed-files" th:name="allowedFileTypes" type="text" class="textfield"
-                   th:placeholder="#{assignment.allowed_files.enter}"/>
-        </div>
-        <div class="form-buttons">
-            <button type=button class="warning text-button" th:text="#{general.cancel}" onclick="toggleOverlay('add-assignment-overlay')"></button>
-            <button type=submit class="text-button" th:text="#{general.save}"></button>
-        </div>
-    </form>
-</div>
-
-</body>
-</html>
\ No newline at end of file
+            </form>
+        </dialog>
+    </body>
+</html>
diff --git a/src/main/resources/templates/assignment/add_train.html b/src/main/resources/templates/assignment/add_train.html
index c198d8f993f0385ebdb211356a6b6d83ffffbc4b..19b25ce7ea1ea9ad4e30a050f17ebce8922dd66a 100644
--- a/src/main/resources/templates/assignment/add_train.html
+++ b/src/main/resources/templates/assignment/add_train.html
@@ -18,19 +18,31 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
-
-<div th:fragment="overlay" id="add-train-overlay" class="hidden confirm overlay">
-    <form th:action="@{|/script/train/assignment/${assignment.id}|}" method="post" class="boxed-content">
-        <p class="confirm_text" th:text="#{script.train.add.confirm(${assignment.name})}"></p>
-        <div class="form-buttons">
-            <button type=button class="warning text-button" th:text="#{general.cancel}"
-                    onclick="toggleOverlay('add-train-overlay')"></button>
-            <button type=submit class="text-button" th:text="#{script.train.add}"></button>
-        </div>
-    </form>
-</div>
-
-</body>
-</html>
\ No newline at end of file
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <dialog th:fragment="overlay" id="add-train-overlay" class="dialog">
+            <form
+                th:action="@{|/script/train/assignment/${assignment.id}|}"
+                method="post"
+                class="flex vertical p-7">
+                <h2 class="underlined font-500">Add script train</h2>
+                <p th:text="#{script.train.add.confirm(${assignment.name})}"></p>
+                <div class="flex space-between">
+                    <button
+                        type="button"
+                        class="button p-less"
+                        data-style="outlined"
+                        th:text="#{general.cancel}"
+                        data-cancel></button>
+                    <button
+                        type="submit"
+                        class="button p-less"
+                        th:text="#{script.train.add}"></button>
+                </div>
+            </form>
+        </dialog>
+    </body>
+</html>
diff --git a/src/main/resources/templates/assignment/edit.html b/src/main/resources/templates/assignment/edit.html
index 1303a3ba9d52f356e21e16d193361f8eb94c3e39..ca82a55c3d3df4381c8665c0547535c363328ffe 100644
--- a/src/main/resources/templates/assignment/edit.html
+++ b/src/main/resources/templates/assignment/edit.html
@@ -18,87 +18,163 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
-
-<div th:fragment="overlay" id="edit-overlay" class="hidden overlay">
-    <form th:object="${patch}" th:action="@{|/assignment/${assignment.id}|}" th:method="patch" class="boxed-content">
-        <h1 class="underlined title" th:text="#{assignment.edit}"></h1>
-        <div class="form-group">
-            <label for="assignment-name" th:text="#{general.name}"></label>
-            <input id="assignment-name" th:name="name" th:value="${assignment.name}" th:placeholder="#{general.name.enter}" type="text" class="textfield" required/>
-
-            <label for="assignment-description" th:text="#{general.description}"></label>
-            <textarea id="assignment-description" th:name="description.text" class="textarea" th:value="${assignment.description?.text}"
-                      th:placeholder="#{general.description.enter}"></textarea>
-
-            <label for="assignment-deadline" th:text="#{assignment.deadline}"></label>
-            <input id="assignment-deadline" th:name="deadline" th:placeholder="#{assignment.deadline.enter}" type="text" class="textfield"
-                   th:value="${assignment.deadline == null} ? '' : ${#temporals.format(assignment.deadline, 'dd-MM-yyyy HH:mm')}"
-                   pattern="|[0-3]\d-[0-1]\d-\d\d\d\d [0-2]\d:[0-5]\d">
-
-            <div class="form-separator"></div>
-
-            <label for="assignment-score-type" class="center-label" th:text="#{grading.score_type}"></label>
-            <select class="selectbox" id="assignment-score-type" th:name="scoreType">
-                <option th:each="type : ${T(nl.tudelft.labracore.lib.api.GradeScheme).values()}"
-                        th:selected="${type.name() == assignment.scoreType?.name()}" th:value="${type}"
-                        th:text="#{|grading.type.${#strings.toLowerCase(type.name())}|}"></option>
-            </select>
-
-            <div class="form-separator"></div>
-
-            <div>
-                <label for="assignment-leniency" th:text="#{assignment.leniency}"></label>
-                <div class="tooltip">
-                    <span class="tooltip_icon">?</span>
-                    <p class="tooltip_text" th:text="#{assignment.leniency.help}"></p>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <dialog th:fragment="overlay" id="edit-overlay" class="dialog">
+            <form
+                th:object="${patch}"
+                th:action="@{|/assignment/${assignment.id}|}"
+                th:method="patch"
+                class="flex vertical p-7">
+                <h1 class="underlined font-500" th:text="#{assignment.edit}"></h1>
+
+                <div class="grid col-2 gap-3" style="--col-1: minmax(0, 14rem)">
+                    <label for="assignment-name" th:text="#{general.name}"></label>
+                    <input
+                        id="assignment-name"
+                        th:name="name"
+                        th:value="${assignment.name}"
+                        th:placeholder="#{general.name.enter}"
+                        type="text"
+                        class="textfield"
+                        required />
+
+                    <label for="assignment-description" th:text="#{general.description}"></label>
+                    <textarea
+                        id="assignment-description"
+                        th:name="description.text"
+                        class="textfield"
+                        th:value="${assignment.description?.text}"
+                        th:placeholder="#{general.description.enter}"></textarea>
+
+                    <label for="assignment-deadline" th:text="#{assignment.deadline}"></label>
+                    <input
+                        id="assignment-deadline"
+                        th:name="deadline"
+                        th:placeholder="#{assignment.deadline.enter}"
+                        type="text"
+                        class="textfield"
+                        th:value="${assignment.deadline == null} ? '' : ${#temporals.format(assignment.deadline, 'dd-MM-yyyy HH:mm')}"
+                        pattern="|[0-3]\d-[0-1]\d-\d\d\d\d [0-2]\d:[0-5]\d" />
+
+                    <div class="underlined" style="grid-column: span 2"></div>
+
+                    <label
+                        for="assignment-score-type"
+                        class="center-label"
+                        th:text="#{grading.score_type}"></label>
+                    <select class="textfield" id="assignment-score-type" th:name="scoreType">
+                        <option
+                            th:each="type : ${T(nl.tudelft.labracore.lib.api.GradeScheme).values()}"
+                            th:selected="${type.name() == assignment.scoreType?.name()}"
+                            th:value="${type}"
+                            th:text="#{|grading.type.${#strings.toLowerCase(type.name())}|}"></option>
+                    </select>
+
+                    <div class="underlined" style="grid-column: span 2"></div>
+
+                    <div>
+                        <label for="assignment-leniency" th:text="#{assignment.leniency}"></label>
+                        <div class="tooltip">
+                            <span class="tooltip__control fa-solid fa-question"></span>
+                            <div role="tooltip" style="white-space: initial; min-width: 24rem">
+                                <p th:text="#{assignment.leniency.help}"></p>
+                            </div>
+                        </div>
+                    </div>
+                    <input
+                        id="assignment-leniency"
+                        th:name="lateSubmissionLeniency"
+                        th:value="${assignment.lateSubmissionLeniency}"
+                        type="number"
+                        min="0"
+                        class="textfield" />
+
+                    <div>
+                        <label
+                            for="assignment-accept-late"
+                            th:text="#{assignment.accept_late}"></label>
+                        <div class="tooltip">
+                            <span class="tooltip__control fa-solid fa-question"></span>
+                            <div role="tooltip" style="white-space: initial; min-width: 20rem">
+                                <p th:text="#{assignment.accept_late.help}"></p>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="checkbox">
+                        <input
+                            id="assignment-accept-late"
+                            th:name="acceptLateSubmissions"
+                            th:checked="${assignment.acceptLateSubmissions}"
+                            type="checkbox" />
+                        <label for="assignment-accept-late" th:text="#{general.allow}"></label>
+                    </div>
+
+                    <div class="underlined" style="grid-column: span 2"></div>
+
+                    <div>
+                        <label
+                            for="assignment-submission-limit"
+                            th:text="#{assignment.submission_limit}"></label>
+                        <div class="tooltip">
+                            <span class="tooltip__control fa-solid fa-question"></span>
+                            <div role="tooltip" style="white-space: initial; min-width: 24rem">
+                                <p th:text="#{assignment.submission_limit.help}"></p>
+                            </div>
+                        </div>
+                    </div>
+                    <input
+                        id="assignment-submission-limit"
+                        th:name="submissionLimit"
+                        type="number"
+                        min="0"
+                        class="textfield"
+                        th:value="${assignment.submissionLimit}"
+                        th:placeholder="#{assignment.submission_limit.enter}" />
+
+                    <div>
+                        <label
+                            for="assignment-allowed-files"
+                            th:text="#{assignment.allowed_files}"></label>
+                        <div class="tooltip">
+                            <span class="tooltip__control fa-solid fa-question"></span>
+                            <div
+                                role="tooltip"
+                                style="
+                                    white-space: initial;
+                                    min-width: 24rem;
+                                    transform-origin: bottom left;
+                                    top: initial;
+                                    bottom: 1.2rem;
+                                ">
+                                <p
+                                    class="tooltip_text"
+                                    th:text="#{assignment.allowed_files.help}"></p>
+                            </div>
+                        </div>
+                    </div>
+                    <input
+                        id="assignment-allowed-files"
+                        th:name="allowedFileTypes"
+                        type="text"
+                        class="textfield"
+                        th:value="${assignment.allowedFileTypes == null} ? '' : ${#strings.substring(assignment.allowedFileTypes, 1, #strings.length(assignment.allowedFileTypes)-1)}"
+                        th:placeholder="#{assignment.allowed_files.enter}" />
                 </div>
-            </div>
-            <input id="assignment-leniency" th:name="lateSubmissionLeniency" th:value="${assignment.lateSubmissionLeniency}"
-                   type="number" min="0" class="textfield"/>
-
-            <div>
-                <label for="assignment-accept-late" th:text="#{assignment.accept_late}"></label>
-                <div class="tooltip">
-                    <span class="tooltip_icon">?</span>
-                    <p class="tooltip_text" th:text="#{assignment.accept_late.help}"></p>
-                </div>
-            </div>
-            <div class="checkbox">
-                <input id="assignment-accept-late" th:name="acceptLateSubmissions" th:checked="${assignment.acceptLateSubmissions}" type="checkbox"/>
-                <label for="assignment-accept-late" th:text="#{general.allow}"></label>
-            </div>
-
-            <div class="form-separator"></div>
-
-            <div>
-                <label for="assignment-submission-limit" th:text="#{assignment.submission_limit}"></label>
-                <div class="tooltip">
-                    <span class="tooltip_icon">?</span>
-                    <p class="tooltip_text" th:text="#{assignment.submission_limit.help}"></p>
-                </div>
-            </div>
-            <input id="assignment-submission-limit" th:name="submissionLimit" type="number" min="0" class="textfield"
-                   th:value="${assignment.submissionLimit}" th:placeholder="#{assignment.submission_limit.enter}"/>
-
-            <div>
-                <label for="assignment-allowed-files" th:text="#{assignment.allowed_files}"></label>
-                <div class="tooltip">
-                    <span class="tooltip_icon">?</span>
-                    <p class="tooltip_text" th:text="#{assignment.allowed_files.help}"></p>
+
+                <div class="flex space-between">
+                    <button
+                        type="button"
+                        class="button p-less"
+                        data-style="outlined"
+                        th:text="#{general.cancel}"
+                        data-cancel></button>
+                    <button type="submit" class="button p-less" th:text="#{general.save}"></button>
                 </div>
-            </div>
-            <input id="assignment-allowed-files" th:name="allowedFileTypes" type="text" class="textfield"
-                   th:value="${assignment.allowedFileTypes == null} ? '' : ${#strings.substring(assignment.allowedFileTypes, 1, #strings.length(assignment.allowedFileTypes)-1)}"
-                   th:placeholder="#{assignment.allowed_files.enter}"/>
-        </div>
-        <div class="form-buttons">
-            <button type=button class="warning text-button" th:text="#{general.cancel}" onclick="toggleOverlay('edit-overlay')"></button>
-            <button type=submit class="text-button" th:text="#{general.save}"></button>
-        </div>
-    </form>
-</div>
-
-</body>
-</html>
\ No newline at end of file
+            </form>
+        </dialog>
+    </body>
+</html>
diff --git a/src/main/resources/templates/assignment/import_grades.html b/src/main/resources/templates/assignment/import_grades.html
index 41928917067695ca43f7cffa60fc4c6418234e6c..9145f86e2751e746b3190216e8a945e88c8abc7b 100644
--- a/src/main/resources/templates/assignment/import_grades.html
+++ b/src/main/resources/templates/assignment/import_grades.html
@@ -18,102 +18,155 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
-
-<div th:fragment="overlay" id="import-grades-overlay" class="hidden overlay">
-    <script>
-        function readColumnOptions() {
-            let file = $("#file")[0].files[0];
-            let reader = new FileReader();
-            reader.onload = () => {
-                let headers = reader.result.split("\n")[0].split(";");
-
-                let idColumn = document.getElementById("id-column");
-                let idType = document.getElementById("id-type");
-                let gradeColumn = document.getElementById("grade-column");
-
-                idColumn.disabled = false;
-                idType.disabled = false;
-                gradeColumn.disabled = false;
-                idColumn.innerHTML = "";
-                gradeColumn.innerHTML = "";
-
-                for (let i in headers) {
-                    let idOption = document.createElement("option");
-                    idOption.value = i.toString();
-                    idOption.text = headers[i];
-                    if (i === 0) idOption.selected = true;
-                    idColumn.appendChild(idOption);
-                    let gradeOption = document.createElement("option");
-                    gradeOption.value = i.toString();
-                    gradeOption.text = headers[i];
-                    if (i === 1) gradeOption.selected = true;
-                    gradeColumn.appendChild(gradeOption);
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <dialog th:fragment="overlay" id="import-grades-overlay" class="dialog">
+            <script>
+                function readColumnOptions() {
+                    let file = $("#file")[0].files[0];
+                    let reader = new FileReader();
+                    reader.onload = () => {
+                        let headers = reader.result.split("\n")[0].split(";");
+
+                        let idColumn = document.getElementById("id-column");
+                        let idType = document.getElementById("id-type");
+                        let gradeColumn = document.getElementById("grade-column");
+
+                        idColumn.disabled = false;
+                        idType.disabled = false;
+                        gradeColumn.disabled = false;
+                        idColumn.innerHTML = "";
+                        gradeColumn.innerHTML = "";
+
+                        for (let i in headers) {
+                            let idOption = document.createElement("option");
+                            idOption.value = i.toString();
+                            idOption.text = headers[i];
+                            if (i === 0) idOption.selected = true;
+                            idColumn.appendChild(idOption);
+                            let gradeOption = document.createElement("option");
+                            gradeOption.value = i.toString();
+                            gradeOption.text = headers[i];
+                            if (i === 1) gradeOption.selected = true;
+                            gradeColumn.appendChild(gradeOption);
+                        }
+                    };
+                    reader.readAsText(file);
+
+                    // let fileLabel = document.getElementById("file-label");
+                    // fileLabel.innerText = file.name;
                 }
-            };
-            reader.readAsText(file);
-
-            // let fileLabel = document.getElementById("file-label");
-            // fileLabel.innerText = file.name;
-        }
-    </script>
-    <form th:object="${gradeImport}" th:action="@{|/assignment/${assignment.id}/import/grades|}" method="post" class="boxed-content"  enctype="multipart/form-data">
-        <h1 class="underlined title" th:text="#{assignment.import_grades}"></h1>
-        <div class="form-group">
-
-            <label for="file" th:text="#{general.file}"></label>
-            <input type="file" id="file" name="file" accept="text/csv"
-                   onchange="readColumnOptions()" required/>
-
-            <div class="form-separator"></div>
-
-            <div>
-                <label for="id-column" th:text="#{assignment.import_grades.id_column}"></label>
-                <div class="tooltip">
-                    <span class="tooltip_icon">?</span>
-                    <p class="tooltip_text" th:text="#{assignment.import_grades.id_column.help}"></p>
+            </script>
+
+            <form
+                th:object="${gradeImport}"
+                th:action="@{|/assignment/${assignment.id}/import/grades|}"
+                method="post"
+                class="flex vertical p-7"
+                enctype="multipart/form-data">
+                <h2 class="underlined font-500" th:text="#{assignment.import_grades}"></h2>
+
+                <div class="grid col-2 gap-3" style="--col-1: minmax(0, 10rem)">
+                    <label for="file" th:text="#{general.file}"></label>
+                    <input
+                        type="file"
+                        id="file"
+                        name="file"
+                        accept="text/csv"
+                        onchange="readColumnOptions()"
+                        required />
+
+                    <div class="underlined" style="grid-column: span 2"></div>
+
+                    <div>
+                        <label
+                            for="id-column"
+                            th:text="#{assignment.import_grades.id_column}"></label>
+                        <div class="tooltip">
+                            <span class="tooltip__control fa-solid fa-question"></span>
+                            <div role="tooltip" style="white-space: initial; min-width: 20rem">
+                                <p th:text="#{assignment.import_grades.id_column.help}"></p>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="flex vertical gap-3">
+                        <select
+                            id="id-column"
+                            th:name="idColumn"
+                            class="textfield"
+                            disabled
+                            required></select>
+                        <select
+                            id="id-type"
+                            aria-label="Identifier type"
+                            th:name="idType"
+                            class="textfield"
+                            disabled
+                            required>
+                            <option
+                                th:each="type : ${T(nl.tudelft.submit.enums.GradeImportIdType).values()}"
+                                th:value="${type}"
+                                th:text="#{|assignment.import_grades.id_type.${#strings.toLowerCase(type)}|}"></option>
+                        </select>
+                    </div>
+
+                    <div>
+                        <label
+                            for="grade-column"
+                            th:text="#{assignment.import_grades.grade_column}"></label>
+                        <div class="tooltip">
+                            <span class="tooltip__control fa-solid fa-question"></span>
+                            <div role="tooltip" style="white-space: initial; min-width: 20rem">
+                                <p th:text="#{assignment.import_grades.grade_column.help}"></p>
+                            </div>
+                        </div>
+                    </div>
+                    <select
+                        id="grade-column"
+                        th:name="gradeColumn"
+                        class="textfield"
+                        disabled
+                        required></select>
+
+                    <div>
+                        <label for="headers" th:text="#{general.headers}"></label>
+                        <div class="tooltip">
+                            <span class="tooltip__control fa-solid fa-question"></span>
+                            <div
+                                role="tooltip"
+                                style="
+                                    white-space: initial;
+                                    min-width: 20rem;
+                                    transform-origin: bottom left;
+                                    top: initial;
+                                    bottom: 1.2rem;
+                                ">
+                                <p th:text="#{general.headers.help}"></p>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="checkbox">
+                        <input id="headers" th:name="hasHeaders" type="checkbox" />
+                        <label for="headers" th:text="#{general.headers.enter}"></label>
+                    </div>
                 </div>
-            </div>
-            <div class="form-vstack">
-                <select id="id-column" th:name="idColumn" class="selectbox" disabled required>
-                </select>
-                <select id="id-type" aria-label="Identifier type" th:name="idType" class="selectbox" disabled required>
-                    <option th:each="type : ${T(nl.tudelft.submit.enums.GradeImportIdType).values()}"
-                            th:value="${type}" th:text="#{|assignment.import_grades.id_type.${#strings.toLowerCase(type)}|}"></option>
-                </select>
-            </div>
-
-            <div>
-                <label for="grade-column" th:text="#{assignment.import_grades.grade_column}"></label>
-                <div class="tooltip">
-                    <span class="tooltip_icon">?</span>
-                    <p class="tooltip_text" th:text="#{assignment.import_grades.grade_column.help}"></p>
-                </div>
-            </div>
-            <select id="grade-column" th:name="gradeColumn" class="selectbox" disabled required>
-            </select>
-
-            <div>
-                <label for="headers" th:text="#{general.headers}"></label>
-                <div class="tooltip">
-                    <span class="tooltip_icon">?</span>
-                    <p class="tooltip_text" th:text="#{general.headers.help}"></p>
+
+                <div class="flex space-between">
+                    <button
+                        type="button"
+                        class="button p-less"
+                        data-style="outlined"
+                        th:text="#{general.cancel}"
+                        data-cancel></button>
+                    <button
+                        type="submit"
+                        class="button p-less"
+                        th:text="#{general.import}"></button>
                 </div>
-            </div>
-            <div class="checkbox">
-                <input id="headers" th:name="hasHeaders" type="checkbox"/>
-                <label for="headers" th:text="#{general.headers.enter}"></label>
-            </div>
-
-        </div>
-        <div class="form-buttons">
-            <button type=button class="warning text-button" th:text="#{general.cancel}"
-                    onclick="toggleOverlay('import-grades-overlay')"></button>
-            <button type=submit class="text-button" th:text="#{general.import}"></button>
-        </div>
-    </form>
-</div>
-
-</body>
-</html>
\ No newline at end of file
+            </form>
+        </dialog>
+    </body>
+</html>
diff --git a/src/main/resources/templates/assignment/statistics.html b/src/main/resources/templates/assignment/statistics.html
index 81659b2f9c43fd6a06afbfbb8b3f28e8e11dad67..6f4fbfabf6fc1280b1d7267fd7b23f681efbc7c4 100644
--- a/src/main/resources/templates/assignment/statistics.html
+++ b/src/main/resources/templates/assignment/statistics.html
@@ -18,83 +18,57 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
-      layout:decorate="~{container}">
-<head>
-    <script src="/webjars/chartjs/Chart.min.js"></script>
-
-    <title th:text="#{general.statistics}"></title>
-</head>
-
-<body>
-
-<div layout:fragment="breadcrumbs">
-    <a th:href="@{/edition/enrolled}" th:text="#{edition.editions}"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{|/edition/${assignment.module.edition.id}|}" th:text="|${edition.course.name} - ${edition.name}|"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:if="${@authorizationService.canViewModule(assignment.module.id)}" th:href="@{|/module/${assignment.module.id}|}" th:text="${assignment.module.name}"></a>
-    <a th:unless="${@authorizationService.canViewModule(assignment.module.id)}" th:href="@{|/module/${assignment.module.id}/groups|}" th:text="${assignment.module.name}"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{|/assignment/${assignment.id}|}" th:text="${assignment.name}"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{|/assignment/${assignment.id}/statistics|}" th:text="#{general.statistics}"></a>
-</div>
-
-<div layout:fragment="sidebar">
-    <a th:unless="${@authorizationService.isStudentInAssignment(assignment.id)}" class="sidebar_item" th:href="@{|/assignment/${assignment.id}|}" th:text="#{general.details}"></a>
-    <div class="sidebar_group">
-        <a th:if="${@authorizationService.canViewAssignmentStatistics(assignment.id)}" class="sidebar_item" th:href="@{|/assignment/${assignment.id}/statistics|}" th:text="#{general.statistics}"></a>
-    </div>
-    <a th:if="${@authorizationService.canViewAssignmentSubmissions(assignment.id)}" class="sidebar_item" th:href="@{|/assignment/${assignment.id}/submissions|}" th:text="#{assignment.submissions}"></a>
-</div>
-
-<div layout:fragment="content">
-
-    <div th:with="edition = ${assignment.module.edition}" th:remove="tag">
-        <div th:replace="~{announcement/edition :: top}"></div>
-    </div>
-
-    <h1 class="title" th:text="${assignment.name}"></h1>
-
-    <div class="tabs">
-        <a th:href="@{|/assignment/${assignment.id}/statistics|}" class="active tab" th:text="#{general.statistics}"></a>
-        <a th:href="@{|/assignment/${assignment.id}/statistics-table|}" class="tab" th:text="#{general.table}"></a>
-    </div>
-
-    <div class="info_row">
-        <div class="card_stats card">
-            <div class="card_stats_row">
-                <span th:text="#{statistics.total_students}"></span>
-                <span th:text="${statistics.totalStudents}"></span>
-            </div>
-            <div class="card_stats_row">
-                <span th:text="#{statistics.total_groups}"></span>
-                <span th:text="${statistics.totalGroups}"></span>
-            </div>
-            <div class="card_stats_row">
-                <span th:text="#{statistics.total_submissions}"></span>
-                <span th:text="${statistics.totalSubmissions}"></span>
-            </div>
-        </div>
-        <div th:if="${statistics.averageScore != null or statistics.medianScore != null}" class="card_stats card">
-            <div th:if="${statistics.averageScore != null}" class="card_stats_row">
-                <span th:text="#{statistics.average_score}"></span>
-                <span th:text="${statistics.averageScore}"></span>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+    layout:decorate="~{container}">
+    <head>
+        <script src="/webjars/chartjs/Chart.min.js"></script>
+
+        <title th:text="#{general.statistics}"></title>
+    </head>
+
+    <body>
+        <main layout:fragment="content">
+            <div layout:replace="~{assignment/top :: top}"></div>
+
+            <div class="surface">
+                <div class="flex vertical gap-3">
+                    <div class="flex space-between underlined pil-3">
+                        <span th:text="#{statistics.total_students}"></span>
+                        <span th:text="${statistics.totalStudents}"></span>
+                    </div>
+                    <div class="flex space-between underlined pil-3">
+                        <span th:text="#{statistics.total_groups}"></span>
+                        <span th:text="${statistics.totalGroups}"></span>
+                    </div>
+                    <div class="flex space-between underlined pil-3">
+                        <span th:text="#{statistics.total_submissions}"></span>
+                        <span th:text="${statistics.totalSubmissions}"></span>
+                    </div>
+                </div>
+                <th:block
+                    th:if="${statistics.averageScore != null or statistics.medianScore != null}">
+                    <div
+                        th:if="${statistics.averageScore != null}"
+                        class="flex underlined space-between pil-3">
+                        <span th:text="#{statistics.average_score}"></span>
+                        <span th:text="${statistics.averageScore}"></span>
+                    </div>
+                    <div
+                        th:if="${statistics.medianScore != null}"
+                        class="flex underlined space-between pil-3">
+                        <span th:text="#{statistics.median_score}"></span>
+                        <span th:text="${statistics.medianScore}"></span>
+                    </div>
+                </th:block>
             </div>
-            <div th:if="${statistics.medianScore != null}" class="card_stats_row">
-                <span th:text="#{statistics.median_score}"></span>
-                <span th:text="${statistics.medianScore}"></span>
-            </div>
-        </div>
-    </div>
-
-    <div class="info_row">
-        <div th:replace="~{statistics/view :: pass-fail-script}"></div>
-        <div th:replace="~{statistics/view :: score-distribution}"></div>
-    </div>
 
-</div>
-
-</body>
+            <div class="grid col-2">
+                <div th:replace="~{statistics/view :: pass-fail-script}"></div>
+                <div th:replace="~{statistics/view :: score-distribution}"></div>
+            </div>
+        </main>
+    </body>
 </html>
diff --git a/src/main/resources/templates/assignment/statistics_table.html b/src/main/resources/templates/assignment/statistics_table.html
index 39ce6d85850daa61b1ef537002028f13f8987ffc..837f8b5283744a0a6e04fc78c05a13d64838c3d0 100644
--- a/src/main/resources/templates/assignment/statistics_table.html
+++ b/src/main/resources/templates/assignment/statistics_table.html
@@ -18,87 +18,145 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
-      layout:decorate="~{container}">
-<head>
-    <title th:text="#{general.statistics}"></title>
-</head>
-
-<body>
-
-<div layout:fragment="breadcrumbs">
-    <a th:href="@{/edition/enrolled}" th:text="#{edition.editions}"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{|/edition/${assignment.module.edition.id}|}" th:text="|${edition.course.name} - ${edition.name}|"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:if="${@authorizationService.canViewModule(assignment.module.id)}" th:href="@{|/module/${assignment.module.id}|}" th:text="${assignment.module.name}"></a>
-    <a th:unless="${@authorizationService.canViewModule(assignment.module.id)}" th:href="@{|/module/${assignment.module.id}/groups|}" th:text="${assignment.module.name}"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{|/assignment/${assignment.id}|}" th:text="${assignment.name}"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{|/assignment/${assignment.id}/statistics|}" th:text="#{general.statistics}"></a>
-</div>
-
-<div layout:fragment="sidebar">
-    <a th:unless="${@authorizationService.isStudentInAssignment(assignment.id)}" class="sidebar_item" th:href="@{|/assignment/${assignment.id}|}" th:text="#{general.details}"></a>
-    <div class="sidebar_group">
-        <a th:if="${@authorizationService.canViewAssignmentStatistics(assignment.id)}" class="sidebar_item" th:href="@{|/assignment/${assignment.id}/statistics|}" th:text="#{general.statistics}"></a>
-    </div>
-    <a th:if="${@authorizationService.canViewAssignmentSubmissions(assignment.id)}" class="sidebar_item" th:href="@{|/assignment/${assignment.id}/submissions|}" th:text="#{assignment.submissions}"></a>
-</div>
-
-<div layout:fragment="content">
-
-    <div th:with="edition = ${assignment.module.edition}" th:remove="tag">
-        <div th:replace="~{announcement/edition :: top}"></div>
-    </div>
-
-    <h1 class="title" th:text="${assignment.name}"></h1>
-
-    <div class="tabs">
-        <a th:href="@{|/assignment/${assignment.id}/statistics|}" class="tab" th:text="#{general.statistics}"></a>
-        <a th:href="@{|/assignment/${assignment.id}/statistics-table|}" class="active tab" th:text="#{general.table}"></a>
-    </div>
-
-    <div class="title-and-search">
-        <h2 class="subtitle"></h2>
-
-        <form id="filter-form" class="title-and-search_elements">
-            <select name="status" aria-label="Filter on status" class="selectbox" onchange="$('#filter-form').submit()">
-                <option value="all" th:text="#{grading.any_status}"></option>
-                <option th:each="status : ${T(nl.tudelft.submit.enums.GradeStatistics).values()}" th:selected="${#strings.toString(param.status) == status.name()}" th:value="${status.name()}" th:text="#{|grading.${#strings.toLowerCase(status.name())}|}"></option>
-            </select>
-            <select name="version" aria-label="Filter on version" class="selectbox" onchange="$('#filter-form').submit()">
-                <option value=-1 th:text="#{assignment.any_version}"></option>
-                <option th:each="version : ${versions}" th:selected="${#strings.toString(param.version) == #strings.toString(version.id)}" th:value="${version.id}" th:text="${version.name}"></option>
-            </select>
-            <div class="search">
-                <input name="q" class="search_field" th:value="${param.q}" aria-label="Student search" type="search" th:placeholder="#{module.student_search}"/>
-                <button class="search_button" type="submit"><span class="fas fa-search"></span></button>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+    layout:decorate="~{container}">
+    <head>
+        <title th:text="#{general.statistics}"></title>
+    </head>
+
+    <body>
+        <div layout:fragment="breadcrumbs">
+            <a th:href="@{/edition/enrolled}" th:text="#{edition.editions}"></a>
+            <span>&nbsp;&gt;&nbsp;</span>
+            <a
+                th:href="@{|/edition/${assignment.module.edition.id}|}"
+                th:text="|${edition.course.name} - ${edition.name}|"></a>
+            <span>&nbsp;&gt;&nbsp;</span>
+            <a
+                th:if="${@authorizationService.canViewModule(assignment.module.id)}"
+                th:href="@{|/module/${assignment.module.id}|}"
+                th:text="${assignment.module.name}"></a>
+            <a
+                th:unless="${@authorizationService.canViewModule(assignment.module.id)}"
+                th:href="@{|/module/${assignment.module.id}/groups|}"
+                th:text="${assignment.module.name}"></a>
+            <span>&nbsp;&gt;&nbsp;</span>
+            <a th:href="@{|/assignment/${assignment.id}|}" th:text="${assignment.name}"></a>
+            <span>&nbsp;&gt;&nbsp;</span>
+            <a
+                th:href="@{|/assignment/${assignment.id}/statistics|}"
+                th:text="#{general.statistics}"></a>
+        </div>
+
+        <div layout:fragment="sidebar">
+            <a
+                th:unless="${@authorizationService.isStudentInAssignment(assignment.id)}"
+                class="sidebar_item"
+                th:href="@{|/assignment/${assignment.id}|}"
+                th:text="#{general.details}"></a>
+            <div class="sidebar_group">
+                <a
+                    th:if="${@authorizationService.canViewAssignmentStatistics(assignment.id)}"
+                    class="sidebar_item"
+                    th:href="@{|/assignment/${assignment.id}/statistics|}"
+                    th:text="#{general.statistics}"></a>
+            </div>
+            <a
+                th:if="${@authorizationService.canViewAssignmentSubmissions(assignment.id)}"
+                class="sidebar_item"
+                th:href="@{|/assignment/${assignment.id}/submissions|}"
+                th:text="#{assignment.submissions}"></a>
+        </div>
+
+        <div layout:fragment="content">
+            <div th:with="edition = ${assignment.module.edition}" th:remove="tag">
+                <div th:replace="~{announcement/edition :: top}"></div>
             </div>
-        </form>
-    </div>
 
-    <table class="table">
-        <tr class="table_header">
-            <th th:text="#{role.student}"></th>
-            <th th:text="#{group.group}"></th>
-            <th th:text="#{assignment.version}"></th>
-            <th th:text="#{grading.grade}"></th>
-            <th></th>
-        </tr>
-        <tr th:each="row : ${data}">
-            <td th:text="${row.person.username}"></td>
-            <td>
-                <a th:if="${row.group != null and @authorizationService.canViewGroup(row.group.id)}" class="text-button" th:href="@{|/group/${row.group.id}|}" th:text="${row.group.name}"></a>
-                <span th:unless="${row.group != null and @authorizationService.canViewGroup(row.group.id)}" th:text="${row.group?.name} ?: #{module.no_group}"></span>
-            </td>
-            <td th:text="${row.version} ?: #{assignment.no_version}"></td>
-            <td th:text="${row.grade == null} ? #{grading.no_grade} : ${@gradeService.getScoreDisplayString(row.grade)}"></td>
-        </tr>
-    </table>
+            <h1 class="title" th:text="${assignment.name}"></h1>
+
+            <div class="tabs">
+                <a
+                    th:href="@{|/assignment/${assignment.id}/statistics|}"
+                    class="tab"
+                    th:text="#{general.statistics}"></a>
+                <a
+                    th:href="@{|/assignment/${assignment.id}/statistics-table|}"
+                    class="active tab"
+                    th:text="#{general.table}"></a>
+            </div>
 
-</div>
+            <div class="title-and-search">
+                <h2 class="subtitle"></h2>
+
+                <form id="filter-form" class="title-and-search_elements">
+                    <select
+                        name="status"
+                        aria-label="Filter on status"
+                        class="selectbox"
+                        onchange="$('#filter-form').submit()">
+                        <option value="all" th:text="#{grading.any_status}"></option>
+                        <option
+                            th:each="status : ${T(nl.tudelft.submit.enums.GradeStatistics).values()}"
+                            th:selected="${#strings.toString(param.status) == status.name()}"
+                            th:value="${status.name()}"
+                            th:text="#{|grading.${#strings.toLowerCase(status.name())}|}"></option>
+                    </select>
+                    <select
+                        name="version"
+                        aria-label="Filter on version"
+                        class="selectbox"
+                        onchange="$('#filter-form').submit()">
+                        <option value="-1" th:text="#{assignment.any_version}"></option>
+                        <option
+                            th:each="version : ${versions}"
+                            th:selected="${#strings.toString(param.version) == #strings.toString(version.id)}"
+                            th:value="${version.id}"
+                            th:text="${version.name}"></option>
+                    </select>
+                    <div class="search">
+                        <input
+                            name="q"
+                            class="search_field"
+                            th:value="${param.q}"
+                            aria-label="Student search"
+                            type="search"
+                            th:placeholder="#{module.student_search}" />
+                        <button class="search_button" type="submit">
+                            <span class="fas fa-search"></span>
+                        </button>
+                    </div>
+                </form>
+            </div>
 
-</body>
-</html>
\ No newline at end of file
+            <table class="table">
+                <tr class="table_header">
+                    <th th:text="#{role.student}"></th>
+                    <th th:text="#{group.group}"></th>
+                    <th th:text="#{assignment.version}"></th>
+                    <th th:text="#{grading.grade}"></th>
+                    <th></th>
+                </tr>
+                <tr th:each="row : ${data}">
+                    <td th:text="${row.person.username}"></td>
+                    <td>
+                        <a
+                            th:if="${row.group != null and @authorizationService.canViewGroup(row.group.id)}"
+                            class="text-button"
+                            th:href="@{|/group/${row.group.id}|}"
+                            th:text="${row.group.name}"></a>
+                        <span
+                            th:unless="${row.group != null and @authorizationService.canViewGroup(row.group.id)}"
+                            th:text="${row.group?.name} ?: #{module.no_group}"></span>
+                    </td>
+                    <td th:text="${row.version} ?: #{assignment.no_version}"></td>
+                    <td
+                        th:text="${row.grade == null} ? #{grading.no_grade} : ${@gradeService.getScoreDisplayString(row.grade)}"></td>
+                </tr>
+            </table>
+        </div>
+    </body>
+</html>
diff --git a/src/main/resources/templates/assignment/submissions.html b/src/main/resources/templates/assignment/submissions.html
index 6134333f1984f7a6e0d038ea51718686f31f8468..851137bcb4da57d6929b420cb9fe373448e7291c 100644
--- a/src/main/resources/templates/assignment/submissions.html
+++ b/src/main/resources/templates/assignment/submissions.html
@@ -18,147 +18,182 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
-      layout:decorate="~{container}">
-<head>
-    <title th:text="${assignment.name}"></title>
-    <script th:inline="javascript">
-        function filterLatest(checkbox) {
-            document.getElementById("latest-hidden").value = checkbox.checked;
-            document.getElementById("search-form").submit();
-        }
-        function selectAll(checkbox) {
-            for (let box of $("[id^=checkbox]")) {
-                box.checked = checkbox.checked;
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+    layout:decorate="~{container}">
+    <head>
+        <title th:text="${assignment.name}"></title>
+        <script th:inline="javascript">
+            function filterLatest(checkbox) {
+                document.getElementById("latest-hidden").value = checkbox.checked;
+                document.getElementById("search-form").submit();
             }
-        }
-        function downloadSelected() {
-            let toDownload = []
-            for (let box of $("[id^=checkbox]")) {
-                if (box.id === "checkbox-all") continue;
-                if (box.checked) {
-                    toDownload.push(parseInt(box.id.replace("checkbox-", "")));
+            function selectAll(checkbox) {
+                for (let box of $("[id^=checkbox]")) {
+                    box.checked = checkbox.checked;
                 }
             }
-            /*<![CDATA[*/
-            let assignmentId = /*[[${assignment.id}]]*/ 0;
-            /*]]>*/
-            window.open("/submission/assignment/" + assignmentId + "/download?submissions=" + toDownload.join(","))
-        }
-        function toggleCollapse(lId, aId) {
-            const list = $(`#${lId}`);
-            list.find(`.list_item:not([data-item="${aId}"])`).addClass("collapsed");
-            list.find(`[data-item="${aId}"]`).toggleClass("collapsed");
-        }
-    </script>
-</head>
-
-<body>
-
-<div layout:fragment="breadcrumbs">
-    <a th:href="@{/edition/enrolled}" th:text="#{edition.editions}"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{|/edition/${assignment.module.edition.id}|}" th:text="|${edition.course.name} - ${edition.name}|"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{|/module/${assignment.module.id}|}" th:text="${assignment.module.name}"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{|/assignment/${assignment.id}|}" th:text="${assignment.name}"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{|/assignment/${assignment.id}/submissions|}" th:text="#{assignment.submissions}"></a>
-</div>
-
-<div th:classappend="${@authorizationService.canViewAssignment(assignment.id)} ? '' : 'hidden'" layout:fragment="sidebar">
-    <a class="sidebar_item" th:href="@{|/assignment/${assignment.id}|}" th:text="#{general.details}"></a>
-    <a class="sidebar_item" th:href="@{|/assignment/${assignment.id}/submissions|}" th:text="#{assignment.submissions}"></a>
-</div>
-
-<div layout:fragment="content">
-
-    <div th:with="edition = ${assignment.module.edition}" th:remove="tag">
-        <div th:replace="~{announcement/edition :: top}"></div>
-    </div>
-
-    <div class="underlined title-and-options">
-        <h1 class="title" th:text="${assignment.name}"></h1>
-        <div class="title-and-options_options">
-            <div class="checkbox">
-                <input id="show-latest" type="checkbox" th:checked="${param.latest != null and param.latest ? true : false}" onchange="filterLatest(this)"/>
-                <label for="show-latest" th:text="#{assignment.show_latest_submissions}"></label>
-            </div>
-            <button class="text-button" th:text="#{assignment.download_selected}" onclick="downloadSelected()"></button>
-        </div>
-    </div>
-
-    <div class="title-and-search">
-        <h2 class="subtitle" th:text="#{assignment.submissions}"></h2>
-
-        <div class="title-and-search_elements">
-            <form id="search-form" class="search">
-                <input id="latest-hidden" name="latest" type="hidden" th:value="${param.latest != null and param.latest ? true : false}"/>
-                <input id="search" name="q" class="search_field" aria-label="Submission search" type="search" th:value="${param.q}" th:placeholder="#{assignment.submission_search}"/>
-                <button class="search_button" type="submit"><span class="fas fa-search"></span></button>
+            function downloadSelected() {
+                let toDownload = [];
+                for (let box of $("[id^=checkbox]")) {
+                    if (box.id === "checkbox-all") continue;
+                    if (box.checked) {
+                        toDownload.push(parseInt(box.id.replace("checkbox-", "")));
+                    }
+                }
+                /*<![CDATA[*/
+                let assignmentId = /*[[${assignment.id}]]*/ 0;
+                /*]]>*/
+                window.open(
+                    "/submission/assignment/" +
+                        assignmentId +
+                        "/download?submissions=" +
+                        toDownload.join(",")
+                );
+            }
+            function toggleCollapse(lId, aId) {
+                const list = $(`#${lId}`);
+                list.find(`.list_item:not([data-item="${aId}"])`).addClass("collapsed");
+                list.find(`[data-item="${aId}"]`).toggleClass("collapsed");
+            }
+        </script>
+    </head>
+
+    <body>
+        <main layout:fragment="content">
+            <th:block layout:replace="~{assignment/top :: top}"></th:block>
+
+            <form id="search-form" class="search mb-3">
+                <input
+                    id="latest-hidden"
+                    name="latest"
+                    type="hidden"
+                    th:value="${param.latest != null and param.latest ? true : false}" />
+                <input
+                    id="search"
+                    name="q"
+                    class="search_field"
+                    aria-label="Submission search"
+                    type="search"
+                    th:value="${param.q}"
+                    th:placeholder="#{assignment.submission_search}" />
+                <button class="fa-solid fa-search" type="submit"></button>
             </form>
-        </div>
-    </div>
 
-    <table class="table">
-        <tr class="table_header">
-            <th>
+            <div class="flex space-between align-center mb-5">
                 <div class="checkbox">
-                    <input id="checkbox-all" type="checkbox" onchange="selectAll(this)"/>
-                    <label for="checkbox-all"></label>
-                </div>
-            </th>
-            <th th:text="#{assignment.submission}"></th>
-            <th th:text="#{submission.date_submitted}"></th>
-            <th th:text="#{submission.submitted_by}"></th>
-            <th th:text="#{group.group}"></th>
-            <th th:text="#{grading.grade}"></th>
-            <th></th>
-        </tr>
-        <tr th:each="submission : ${param.latest != null and param.latest ? submissions.?[isLatest] : submissions}">
-            <td>
-                <div class="checkbox">
-                    <input th:id="|checkbox-${submission.id}|" type="checkbox"/>
-                    <label th:for="|checkbox-${submission.id}|"></label>
-                </div>
-            </td>
-            <td>
-                <a class="text-button" th:href="@{|/submission/${submission.id}/download/|}" th:text="#{submission.name(${submission.id})}"></a>
-            </td>
-            <td>
-                <span th:text="${#temporals.format(submission.submissionTime, 'dd-MM-yyyy HH:mm')}"></span>
-                <span class="tag" style="float: none;" th:if="${submission.late}" th:text="#{submission.late}"></span>
-            </td>
-            <td th:text="${submission.submitter.username}"></td>
-            <td>
-                <a th:if="${@authorizationService.canViewGroup(submission.group.id)}" class="text-button" th:href="@{|/group/${submission.group.id}|}" th:text="${submission.group.name}"></a>
-                <span th:unless="${@authorizationService.canViewGroup(submission.group.id)}" th:text="${submission.group.name}"></span>
-            </td>
-            <td class="table_cell-with-buttons">
-                <span th:text="${submission.getMaxGrade() == null} ? #{grading.no_grade} : ${@gradeService.getScoreDisplayString(submission.getMaxGrade())}"></span>
-                <div>
-                    <button th:if="${@authorizationService.canGradeSubmission(submission.id)}" class="icon-button"
-                            th:onclick="|toggleOverlay('feedback-${submission.id}-overlay')|"><i class="far fa-edit"></i></button>
-                    <button th:if="${@authorizationService.canResubmitSubmission(submission.id)}" class="icon-button"
-                            th:onclick="|jQuery.post('/submission/${submission.id}/resubmit'); this.disabled = true;|"><i class="fas fa-redo-alt"></i></button>
-                    <button th:if="${@authorizationService.canRemoveSubmissionGrade(submission.id) and submission.getMaxGrade() != null}" class="warning icon-button"
-                            th:onclick="|toggleOverlay('remove-grade-${submission.id}-overlay')|"><i class="far fa-trash-alt"></i></button>
+                    <input
+                        id="show-latest"
+                        type="checkbox"
+                        th:checked="${param.latest != null and param.latest ? true : false}"
+                        onchange="filterLatest(this)" />
+                    <label
+                        for="show-latest"
+                        th:text="#{assignment.show_latest_submissions}"></label>
                 </div>
-            </td>
-        </tr>
-    </table>
-
-    <div th:each="submission : ${submissions}">
-        <th:block th:with="group = ${submission.group}">
-            <div th:replace="~{submission/feedback :: overlay('assignment')}"></div>
-        </th:block>
-        <div th:replace="~{submission/remove_grade :: overlay}"></div>
-    </div>
-
-    <div th:replace="~{submission/feedback :: script}"></div>
+                <button
+                    class="button"
+                    data-style="outlined"
+                    th:text="#{assignment.download_selected}"
+                    onclick="downloadSelected()"></button>
+            </div>
 
-</div>
+            <div class="flex vertical">
+                <table class="table" data-style="surface">
+                    <tr class="table__header">
+                        <td class="fit-content">
+                            <div class="checkbox">
+                                <input
+                                    id="checkbox-all"
+                                    type="checkbox"
+                                    onchange="selectAll(this)" />
+                                <label for="checkbox-all"></label>
+                            </div>
+                        </td>
+                        <th th:text="#{assignment.submission}"></th>
+                        <th th:text="#{submission.date_submitted}"></th>
+                        <th th:text="#{submission.submitted_by}"></th>
+                        <th th:text="#{group.group}"></th>
+                        <th th:text="#{grading.grade}"></th>
+                        <th></th>
+                    </tr>
+                    <tr
+                        th:if="${(param.latest != null and param.latest ? submissions.?[isLatest] : submissions).isEmpty()}">
+                        <td colspan="7">No submissions</td>
+                    </tr>
+                    <tr
+                        th:each="submission : ${param.latest != null and param.latest ? submissions.?[isLatest] : submissions}">
+                        <td class="fit-content">
+                            <div class="checkbox">
+                                <input th:id="|checkbox-${submission.id}|" type="checkbox" />
+                                <label th:for="|checkbox-${submission.id}|"></label>
+                            </div>
+                        </td>
+                        <td>
+                            <a
+                                class="link"
+                                th:href="@{|/submission/${submission.id}/download/|}"
+                                th:text="#{submission.name(${submission.id})}"></a>
+                        </td>
+                        <td>
+                            <span
+                                th:text="${#temporals.format(submission.submissionTime, 'dd-MM-yyyy HH:mm')}"></span>
+                            <span
+                                class="chip"
+                                th:if="${submission.late}"
+                                th:text="#{submission.late}"></span>
+                        </td>
+                        <td th:text="${submission.submitter.displayName}"></td>
+                        <td>
+                            <a
+                                th:if="${@authorizationService.canViewGroup(submission.group.id)}"
+                                class="link"
+                                th:href="@{|/group/${submission.group.id}|}"
+                                th:text="${submission.group.name}"></a>
+                            <span
+                                th:unless="${@authorizationService.canViewGroup(submission.group.id)}"
+                                th:text="${submission.group.name}"></span>
+                        </td>
+                        <td class="flex gap-3">
+                            <span
+                                th:text="${submission.getMaxGrade() == null} ? #{grading.no_grade} : ${@gradeService.getScoreDisplayString(submission.getMaxGrade())}"></span>
+                            <div>
+                                <button
+                                    th:if="${@authorizationService.canGradeSubmission(submission.id)}"
+                                    class="button p-min"
+                                    data-style="outlined"
+                                    th:data-dialog="|feedback-${submission.id}-overlay|">
+                                    <i class="far fa-edit"></i>
+                                </button>
+                                <button
+                                    th:if="${@authorizationService.canResubmitSubmission(submission.id)}"
+                                    class="button p-min"
+                                    data-style="outlined"
+                                    th:onclick="|jQuery.post('/submission/${submission.id}/resubmit'); this.disabled = true;|">
+                                    <i class="fas fa-redo-alt"></i>
+                                </button>
+                                <button
+                                    th:if="${@authorizationService.canRemoveSubmissionGrade(submission.id) and submission.getMaxGrade() != null}"
+                                    class="button p-min"
+                                    data-type="error"
+                                    data-style="outlined"
+                                    th:data-dialog="|remove-grade-${submission.id}-overlay|">
+                                    <i class="far fa-trash-alt"></i>
+                                </button>
+                            </div>
+                        </td>
+                    </tr>
+                </table>
+            </div>
 
-</body>
-</html>
\ No newline at end of file
+            <div th:each="submission : ${submissions}">
+                <th:block th:with="group = ${submission.group}">
+                    <div th:replace="~{submission/feedback :: overlay('assignment')}"></div>
+                </th:block>
+                <div th:replace="~{submission/remove_grade :: overlay}"></div>
+            </div>
+        </main>
+    </body>
+</html>
diff --git a/src/main/resources/templates/assignment/submit.html b/src/main/resources/templates/assignment/submit.html
index a4d38c3ab83d1483a8a36afb07c00fbb147ab1b4..d62becf7b29d3c78c25e638f3057f7c1f00c46ee 100644
--- a/src/main/resources/templates/assignment/submit.html
+++ b/src/main/resources/templates/assignment/submit.html
@@ -18,29 +18,51 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
-
-<div th:fragment="overlay(page)" th:if="${group != null and @authorizationService.canSubmitAssignment(assignment.id, group.id)}" th:id="|submit-${assignment.id}-overlay|" class="hidden overlay">
-    <form th:object="${createSubmissionDto}" th:action="@{|/submission/${page}|}" method="post" class="boxed-content"  enctype="multipart/form-data">
-        <h1 class="underlined title" th:text="#{assignment.submit}"></h1>
-        <div class="form-group">
-            <input th:name="group.id" th:value="${group.id}" type="hidden"/>
-            <input th:name="assignment.id" th:value="${assignment.id}" type="hidden"/>
-
-            <label for="file" th:text="#{general.file}"></label>
-            <input type="file" id="file" name="file"
-                   th:accept="${assignment.allowedFileTypes == null} ? '*' : ${#strings.substring(assignment.allowedFileTypes.toString(), 1, #strings.length(assignment.allowedFileTypes.toString())-1)}"
-                   onchange="readColumnOptions()" required/>
-
-        </div>
-        <div class="form-buttons">
-            <button type=button class="warning text-button" th:text="#{general.cancel}"
-                    th:onclick="|toggleOverlay('submit-${assignment.id}-overlay')|"></button>
-            <button type=submit class="text-button" th:text="#{assignment.submit}"></button>
-        </div>
-    </form>
-</div>
-
-</body>
-</html>
\ No newline at end of file
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <dialog
+            th:fragment="overlay(page)"
+            th:if="${group != null and @authorizationService.canSubmitAssignment(assignment.id, group.id)}"
+            th:id="|submit-${assignment.id}-overlay|"
+            class="dialog">
+            <form
+                th:object="${createSubmissionDto}"
+                th:action="@{|/submission/${page}|}"
+                method="post"
+                class="flex vertical p-7"
+                enctype="multipart/form-data">
+                <h1 class="underlined font-500" th:text="#{assignment.submit}"></h1>
+
+                <div class="grid col-2 align-center gap-3" style="--col-1: minmax(0, 8rem)">
+                    <input th:name="group.id" th:value="${group.id}" type="hidden" />
+                    <input th:name="assignment.id" th:value="${assignment.id}" type="hidden" />
+
+                    <label for="file" th:text="#{general.file}"></label>
+                    <input
+                        type="file"
+                        id="file"
+                        name="file"
+                        th:accept="${assignment.allowedFileTypes == null} ? '*' : ${#strings.substring(assignment.allowedFileTypes.toString(), 1, #strings.length(assignment.allowedFileTypes.toString())-1)}"
+                        onchange="readColumnOptions()"
+                        required />
+                </div>
+
+                <div class="flex space-between">
+                    <button
+                        type="button"
+                        class="button p-less"
+                        data-style="outlined"
+                        th:text="#{general.cancel}"
+                        data-cancel></button>
+                    <button
+                        type="submit"
+                        class="button p-less"
+                        th:text="#{assignment.submit}"></button>
+                </div>
+            </form>
+        </dialog>
+    </body>
+</html>
diff --git a/src/main/resources/templates/assignment/top.html b/src/main/resources/templates/assignment/top.html
new file mode 100644
index 0000000000000000000000000000000000000000..2f27dd6cf2ca1df6557744b4005315796fc3b95e
--- /dev/null
+++ b/src/main/resources/templates/assignment/top.html
@@ -0,0 +1,109 @@
+<!--
+
+    Submit
+    Copyright (C) 2020 - Delft University of Technology
+
+    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 <https://www.gnu.org/licenses/>.
+
+-->
+<!DOCTYPE html>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <div layout:fragment="top">
+            <div class="breadcrumbs mb-5">
+                <a th:href="@{/edition/enrolled}" th:text="#{edition.editions}"></a>
+                <span>&gt;</span>
+                <a
+                    th:href="@{|/edition/${assignment.module.edition.id}|}"
+                    th:text="|${edition.course.name} - ${edition.name}|"></a>
+                <span>&gt;</span>
+                <a
+                    th:if="${@authorizationService.canViewModule(assignment.module.id)}"
+                    th:href="@{|/module/${assignment.module.id}|}"
+                    th:text="${assignment.module.name}"></a>
+                <a
+                    th:unless="${@authorizationService.canViewModule(assignment.module.id)}"
+                    th:href="@{|/module/${assignment.module.id}/groups|}"
+                    th:text="${assignment.module.name}"></a>
+                <span>&gt;</span>
+                <a th:href="@{|/assignment/${assignment.id}|}" th:text="${assignment.name}"></a>
+            </div>
+
+            <div th:with="edition = ${assignment.module.edition}" th:remove="tag">
+                <div layout:replace="~{announcement/edition :: top}"></div>
+            </div>
+
+            <h1 class="font-800 mb-3" th:text="${assignment.name}"></h1>
+
+            <div class="flex gap-3 wrap mb-5">
+                <button
+                    th:if="${(@authorizationService.isStudentInAssignment(assignment.id) and group == null) or (group != null and @authorizationService.canSubmitAssignment(assignment.id, group.id))}"
+                    th:disabled="${group == null or not canMakeSubmission}"
+                    th:data-dialog="|submit-${assignment.id}-overlay|"
+                    class="button"
+                    th:text="#{assignment.submit}"></button>
+                <button
+                    th:if="${@authorizationService.canEditAssignment(assignment.id)}"
+                    class="button"
+                    data-style="outlined"
+                    th:text="#{assignment.edit}"
+                    data-dialog="edit-overlay"></button>
+                <button
+                    th:if="${@authorizationService.canImportAssignmentGrades(assignment.id)}"
+                    class="button"
+                    data-style="outlined"
+                    th:text="#{grading.import}"
+                    data-dialog="import-grades-overlay"></button>
+                <button
+                    th:if="${@authorizationService.canCreateVersion(assignment.id) and versions.isEmpty()}"
+                    class="button"
+                    data-style="outlined"
+                    th:text="#{script.train.add}"
+                    data-dialog="add-train-overlay"></button>
+            </div>
+
+            <div class="tabs mb-5" role="tablist">
+                <a
+                    role="tab"
+                    th:if="${@authorizationService.canViewAssignment(assignment.id)}"
+                    th:aria-selected="${#request.requestURI.matches('.*assignment/[0-9]+')}"
+                    th:href="@{/assignment/{id}(id=${assignment.id})}">
+                    Overview
+                </a>
+                <a
+                    role="tab"
+                    th:if="${@authorizationService.canViewAssignmentSubmissions(assignment.id)}"
+                    th:aria-selected="${#request.requestURI.matches('.*/submissions')}"
+                    th:href="@{/assignment/{id}/submissions(id=${assignment.id})}">
+                    Submissions
+                </a>
+                <a
+                    role="tab"
+                    th:if="${@authorizationService.canViewAssignmentStatistics(assignment.id)}"
+                    th:aria-selected="${#request.requestURI.matches('.*/statistics')}"
+                    th:href="@{/assignment/{id}/statistics(id=${assignment.id})}">
+                    Statistics
+                </a>
+            </div>
+
+            <div th:replace="~{assignment/import_grades :: overlay}"></div>
+            <div th:replace="~{assignment/edit :: overlay}"></div>
+            <div th:replace="~{assignment/submit :: overlay('assignment')}"></div>
+            <div th:replace="~{assignment/add_train :: overlay}"></div>
+        </div>
+    </body>
+</html>
diff --git a/src/main/resources/templates/assignment/view.html b/src/main/resources/templates/assignment/view.html
index 0b1d1510ef9eacf58c8dfb51f7f7c77c87fee0a5..a8b158622deeb34080e8a163c7d0427beedf4343 100644
--- a/src/main/resources/templates/assignment/view.html
+++ b/src/main/resources/templates/assignment/view.html
@@ -18,185 +18,196 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
-      layout:decorate="~{container}">
-<head>
-    <title th:text="${assignment.name}"></title>
-</head>
-
-<body>
-
-<div layout:fragment="breadcrumbs">
-    <a th:href="@{/edition/enrolled}" th:text="#{edition.editions}"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{|/edition/${assignment.module.edition.id}|}" th:text="|${edition.course.name} - ${edition.name}|"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:if="${@authorizationService.canViewModule(assignment.module.id)}" th:href="@{|/module/${assignment.module.id}|}" th:text="${assignment.module.name}"></a>
-    <a th:unless="${@authorizationService.canViewModule(assignment.module.id)}" th:href="@{|/module/${assignment.module.id}/groups|}" th:text="${assignment.module.name}"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{|/assignment/${assignment.id}|}" th:text="${assignment.name}"></a>
-</div>
-
-<div layout:fragment="sidebar">
-    <button th:if="${(@authorizationService.isStudentInAssignment(assignment.id) and group == null) or (group != null and @authorizationService.canSubmitAssignment(assignment.id, group.id))}"
-            th:disabled="${group == null or not canMakeSubmission}"
-            th:onclick="|toggleOverlay('submit-${assignment.id}-overlay')|" class="sidebar_item" th:text="#{assignment.submit}"></button>
-    <a th:unless="${@authorizationService.isStudentInAssignment(assignment.id)}" class="sidebar_item" th:href="@{|/assignment/${assignment.id}|}" th:text="#{general.details}"></a>
-    <div class="sidebar_group">
-        <a th:if="${@authorizationService.canViewAssignmentStatistics(assignment.id)}" class="sidebar_item" th:href="@{|/assignment/${assignment.id}/statistics|}" th:text="#{general.statistics}"></a>
-        <button th:if="${@authorizationService.canImportAssignmentGrades(assignment.id)}" class="sidebar_item" th:text="#{grading.import}" onclick="toggleOverlay('import-grades-overlay')"></button>
-        <button th:if="${@authorizationService.canEditAssignment(assignment.id)}" class="sidebar_item" th:text="#{assignment.edit}" onclick="toggleOverlay('edit-overlay')"></button>
-        <button th:if="${@authorizationService.canCreateVersion(assignment.id) and versions.isEmpty()}" class="sidebar_item" th:text="#{script.train.add}" onclick="toggleOverlay('add-train-overlay')"></button>
-        <button th:if="${isTransparent and @authorizationService.canEditVersion(versions[0].id)}" class="sidebar_item" onclick="toggleOverlay('edit-train-overlay')" th:text="#{script.train.edit}"></button>
-        <button th:if="${isTransparent and @authorizationService.canEditVersion(versions[0].id)}" class="sidebar_item" onclick="toggleOverlay('add-wagon-overlay')" th:text="#{script.wagon.add}"></button>
-    </div>
-    <a th:if="${@authorizationService.canViewAssignmentSubmissions(assignment.id)}" class="sidebar_item" th:href="@{|/assignment/${assignment.id}/submissions|}" th:text="#{assignment.submissions}"></a>
-</div>
-
-<div layout:fragment="content">
-
-    <div th:with="edition = ${assignment.module.edition}" th:remove="tag">
-        <div th:replace="~{announcement/edition :: top}"></div>
-    </div>
-
-    <h1 class="title" th:text="${assignment.name}"></h1>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+    layout:decorate="~{container}">
+    <head>
+        <title th:text="${assignment.name}"></title>
+    </head>
+
+    <body>
+        <main layout:fragment="content">
+            <th:block layout:replace="~{assignment/top :: top}"></th:block>
+
+            <div
+                class="grid mb-5"
+                th:classappend="${@authorizationService.isStudentInAssignment(assignment.id) ? 'col-2' : ''}">
+                <div
+                    th:unless="${@authorizationService.isStudentInAssignment(assignment.id)}"
+                    class="surface">
+                    <div class="underlined flex space-between pil-3">
+                        <span th:text="|#{assignment.deadline}:|"></span>
+                        <span
+                            th:text="${assignment.deadline} ? ${#temporals.format(assignment.deadline, 'dd-MM-yyyy HH:mm')} : #{assignment.no_deadline}"></span>
+                    </div>
+                    <div class="underlined flex space-between pil-3">
+                        <span th:text="|#{assignment.submission_limit}:|"></span>
+                        <span th:text="${assignment.submissionLimit} ?: #{general.none}"></span>
+                    </div>
+                    <div class="underlined flex space-between pil-3">
+                        <span th:text="|#{assignment.allowed_files}:|"></span>
+                        <span
+                            th:text="${assignment.allowedFileTypes.isEmpty()} ? #{general.all} : ${#strings.substring(assignment.allowedFileTypes, 1, #strings.length(assignment.allowedFileTypes)-1)}"></span>
+                    </div>
+                    <div class="underlined flex space-between pil-3">
+                        <span th:text="|#{assignment.leniency}:|"></span>
+                        <span
+                            th:text="${assignment.lateSubmissionLeniency != null && assignment.lateSubmissionLeniency != 0} ? #{general.hours(${assignment.lateSubmissionLeniency})} : #{general.none}"></span>
+                    </div>
+                    <div class="underlined flex space-between pil-3">
+                        <span th:text="|#{assignment.accept_late}:|"></span>
+                        <span
+                            th:text="${assignment.acceptLateSubmissions} ? #{general.yes} : #{general.no}"></span>
+                    </div>
+                </div>
 
-    <div class="info_row">
-        <div th:unless="${@authorizationService.isStudentInAssignment(assignment.id)}" class="card_stats card">
-            <div class="smaller compact card_stats_row">
-                <span th:text="|#{assignment.deadline}:|"></span>
-                <span th:text="${assignment.deadline} ? ${#temporals.format(assignment.deadline, 'dd-MM-yyyy HH:mm')} : #{assignment.no_deadline}"></span>
-            </div>
-            <div class="smaller compact card_stats_row">
-                <span th:text="|#{assignment.submission_limit}:|"></span>
-                <span th:text="${assignment.submissionLimit} ?: #{general.none}"></span>
-            </div>
-            <div class="smaller compact card_stats_row">
-                <span th:text="|#{assignment.allowed_files}:|"></span>
-                <span th:text="${assignment.allowedFileTypes.isEmpty()} ? #{general.all} : ${#strings.substring(assignment.allowedFileTypes, 1, #strings.length(assignment.allowedFileTypes)-1)}"></span>
-            </div>
-            <div class="smaller compact card_stats_row">
-                <span th:text="|#{assignment.leniency}:|"></span>
-                <span th:text="${assignment.lateSubmissionLeniency != null && assignment.lateSubmissionLeniency != 0} ? #{general.hours(${assignment.lateSubmissionLeniency})} : #{general.none}"></span>
-            </div>
-            <div class="smaller compact card_stats_row">
-                <span th:text="|#{assignment.accept_late}:|"></span>
-                <span th:text="${assignment.acceptLateSubmissions} ? #{general.yes} : #{general.no}"></span>
-            </div>
-        </div>
+                <div
+                    th:if="${@authorizationService.isStudentInAssignment(assignment.id)}"
+                    class="surface">
+                    <div class="flex vertical gap-3">
+                        <span class="font-400" th:text="${group?.name} ?: #{group.no_group}"></span>
+                        <span
+                            th:text="${submissions == null or submissions.size() == 0 or submissions[0].getMaxGrade() == null} ? #{group.no_grade} : ${@gradeService.getScoreDisplayString(submissions[0].getMaxGrade())}"></span>
+                    </div>
+                </div>
 
-        <div th:if="${@authorizationService.isStudentInAssignment(assignment.id)}" class="half card">
-            <div class="card_vertical-content">
-                <span th:text="${group?.name} ?: #{group.no_group}"></span>
-                <span th:text="${submissions == null or submissions.size() == 0 or submissions[0].getMaxGrade() == null} ? #{group.no_grade} : ${@gradeService.getScoreDisplayString(submissions[0].getMaxGrade())}"></span>
+                <div
+                    th:if="${@authorizationService.isStudentInAssignment(assignment.id)}"
+                    class="surface flex vertical gap-1">
+                    <div class="underlined flex space-between pil-3">
+                        <span th:text="|#{assignment.deadline}:|"></span>
+                        <span
+                            th:text="${assignment.deadline} ? ${#temporals.format(assignment.deadline, 'dd-MM-yyyy HH:mm')} : #{assignment.no_deadline}"></span>
+                    </div>
+                    <div class="underlined flex space-between pil-3">
+                        <span th:text="|#{assignment.submission_limit}:|"></span>
+                        <span th:text="${assignment.submissionLimit} ?: #{general.none}"></span>
+                    </div>
+                    <div class="underlined flex space-between pil-3">
+                        <span th:text="|#{assignment.allowed_files}:|"></span>
+                        <span
+                            th:text="${assignment.allowedFileTypes.isEmpty()} ? #{general.all} : ${#strings.substring(assignment.allowedFileTypes, 1, #strings.length(assignment.allowedFileTypes)-1)}"></span>
+                    </div>
+                </div>
             </div>
-        </div>
 
-        <button th:if="${@authorizationService.canCreateVersion(assignment.id)}" onclick="toggleOverlay('add-version-overlay')" class="card interactive">
-            <span class="card_icon fas fa-plus-circle"></span>
-            <span th:text="#{version.add}"></span>
-        </button>
+            <div
+                th:unless="${isTransparent or @authorizationService.isStudentInAssignment(assignment.id)}"
+                th:remove="tag">
+                <div class="flex space-between align-center">
+                    <h2 class="font-500" th:text="#{assignment.versions}"></h2>
+                    <button
+                        th:if="${@authorizationService.canCreateVersion(assignment.id)}"
+                        data-dialog="add-version-overlay"
+                        class="button"
+                        data-style="outlined"
+                        th:text="#{version.add}"></button>
+                </div>
 
-        <div th:if="${@authorizationService.isStudentInAssignment(assignment.id)}" class="card_stats half card">
-            <div class="smaller card_stats_row">
-                <span th:text="|#{assignment.deadline}:|"></span>
-                <span th:text="${assignment.deadline} ? ${#temporals.format(assignment.deadline, 'dd-MM-yyyy HH:mm')} : #{assignment.no_deadline}"></span>
+                <ul id="versions" class="divided list" role="list">
+                    <li th:if="${versions.isEmpty()}" class="pil-5 pbl-3">
+                        <span class="list_item_content" th:text="#{assignment.no_versions}"></span>
+                    </li>
+                    <li
+                        th:each="version, iter : ${versions}"
+                        th:if="${@authorizationService.canViewVersion(version.id)}"
+                        class="flex space-between align-center pil-5 pbl-3">
+                        <span th:text="${version.name}"></span>
+                        <div class="flex align-center">
+                            <a
+                                th:if="${@authorizationService.canEditVersion(version.id)}"
+                                class="link"
+                                th:href="@{|/script/train/${version.scriptTrain.id}|}"
+                                th:text="#{script.train}"></a>
+                            <button
+                                th:if="${@authorizationService.canRemoveVersion(version.id)}"
+                                class="button p-min"
+                                data-style="outlined"
+                                data-type="error"
+                                type="button"
+                                th:text="#{general.remove}"
+                                th:data-dialog="|remove-${version.id}-overlay|"></button>
+                        </div>
+                    </li>
+                </ul>
             </div>
-            <div class="smaller card_stats_row">
-                <span th:text="|#{assignment.submission_limit}:|"></span>
-                <span th:text="${assignment.submissionLimit} ?: #{general.none}"></span>
-            </div>
-            <div class="smaller card_stats_row">
-                <span th:text="|#{assignment.allowed_files}:|"></span>
-                <span th:text="${assignment.allowedFileTypes.isEmpty()} ? #{general.all} : ${#strings.substring(assignment.allowedFileTypes, 1, #strings.length(assignment.allowedFileTypes)-1)}"></span>
-            </div>
-        </div>
-    </div>
 
-    <div th:unless="${isTransparent or @authorizationService.isStudentInAssignment(assignment.id)}" th:remove="tag">
-        <h2 class="subtitle" th:text="#{assignment.versions}"></h2>
-        <div id="versions" class="list">
-            <div th:if="${versions.isEmpty()}" class="list_item">
-                <span class="list_item_content" th:text="#{assignment.no_versions}"></span>
-            </div>
-            <div th:each="version, iter : ${versions}" th:if="${@authorizationService.canViewVersion(version.id)}" class="list_item">
-                <span class="list_item_content" th:text="${version.name}"></span>
-                <div class="list_actions">
-                    <a th:if="${@authorizationService.canEditVersion(version.id)}" class="text-button" th:href="@{|/script/train/${version.scriptTrain.id}|}" th:text="#{script.train}"></a>
-                    <button th:if="${@authorizationService.canRemoveVersion(version.id)}" class="warning icon-button" type=button th:onclick="|toggleOverlay('remove-${version.id}-overlay')|"><span class="far fa-trash-alt"></span></button>
+            <div
+                th:unless="${@authorizationService.isStudentInAssignment(assignment.id)}"
+                th:if="${isTransparent}"
+                th:remove="tag">
+                <div class="flex align-center space-between">
+                    <h2 class="font-500" th:text="#{script.train}"></h2>
+                    <div class="flex gap-3">
+                        <button
+                            th:if="${@assignmentService.isTransparent(assignment.id) and @authorizationService.canEditVersion(versions[0].id)}"
+                            class="button p-less"
+                            data-style="outlined"
+                            data-dialog="edit-train-overlay"
+                            th:text="#{script.train.edit}"></button>
+                        <button
+                            th:if="${@assignmentService.isTransparent(assignment.id) and @authorizationService.canEditVersion(versions[0].id)}"
+                            class="button p-less"
+                            data-style="outlined"
+                            data-dialog="add-wagon-overlay"
+                            th:text="#{script.wagon.add}"></button>
+                    </div>
                 </div>
+                <div th:replace="~{version/wagon_list :: list}"></div>
             </div>
-        </div>
-    </div>
 
-    <div th:unless="${@authorizationService.isStudentInAssignment(assignment.id)}" th:if="${isTransparent}" th:remove="tag">
-        <h2 class="subtitle" th:text="#{script.train}"></h2>
-        <div th:replace="~{version/wagon_list :: list}"></div>
-    </div>
-
-    <div th:if="${@authorizationService.isStudentInAssignment(assignment.id)}" th:remove="tag">
-        <h2 class="subtitle" th:text="#{assignment.submissions}"></h2>
-        <table class="table">
-            <tr class="table_header">
-                <th th:text="#{assignment.submission}"></th>
-                <th th:text="#{submission.date_submitted}"></th>
-                <th th:text="#{grading.grade}"></th>
-                <th></th>
-            </tr>
-            <tr th:if="${submissions == null or submissions.isEmpty()}">
-                <td th:text="#{assignment.no_submissions}"></td>
-                <td></td>
-                <td></td>
-                <td></td>
-            </tr>
-            <tr th:if="${submissions != null}" th:each="submission, iter : ${submissions}">
-                <td th:text="#{submission.name(${submissions.size() - iter.index})}"></td>
-                <td th:text="${#temporals.format(submission.submissionTime, 'dd-MM-yyyy HH:mm')}"></td>
-                <td th:text="${submission.getMaxGrade() == null} ? #{group.no_grade} : ${@gradeService.getScoreDisplayString(submission.getMaxGrade())}"></td>
-                <td><div class="table_actions">
-                    <a th:if="${@authorizationService.canDownloadSubmission(submission.id)}" class="text-button"
-                       th:href="@{|/submission/${submission.id}/download|}" th:text="#{general.download}"></a>
-                    <button class="text-button" th:onclick="|toggleOverlay('feedback-${submission.id}-overlay')|" th:text="#{submission.feedback}"></button>
-                </div></td>
-            </tr>
-        </table>
-    </div>
-
-    <div th:replace="~{assignment/import_grades :: overlay}"></div>
-    <div th:replace="~{assignment/edit :: overlay}"></div>
-    <div th:replace="~{version/add :: overlay}"></div>
-    <div th:each="version : ${versions}">
-        <div th:replace="~{version/remove :: overlay}"></div>
-    </div>
-    <div th:replace="~{assignment/submit :: overlay('assignment')}"></div>
-    <div th:replace="~{assignment/add_train :: overlay}"></div>
-    <div th:if="${isTransparent and not @authorizationService.isStudentInAssignment(assignment.id)}" th:remove="tag">
-        <div th:replace="~{version/add_wagon :: overlay}"></div>
-        <div th:replace="~{version/edit_train :: overlay}"></div>
-        <div th:each="wagon : ${train.wagons}" th:remove="tag">
-            <div th:replace="~{version/add_script :: overlay}"></div>
-            <div th:replace="~{version/edit_wagon :: overlay}"></div>
-            <div th:replace="~{version/remove_wagon :: overlay}"></div>
-            <div th:each="script : ${wagon.scripts}" th:remove="tag">
-                <div th:replace="~{version/edit_script :: overlay}"></div>
-                <div th:replace="~{version/remove_script :: overlay}"></div>
+            <div
+                th:if="${@authorizationService.isStudentInAssignment(assignment.id)}"
+                class="flex vertical gap-3">
+                <h2 class="font-500" th:text="#{assignment.submissions}"></h2>
+
+                <table class="table" data-style="surface">
+                    <tr class="table__header">
+                        <th th:text="#{assignment.submission}"></th>
+                        <th th:text="#{submission.date_submitted}"></th>
+                        <th th:text="#{grading.grade}"></th>
+                        <th></th>
+                    </tr>
+                    <tr th:if="${submissions == null or submissions.isEmpty()}">
+                        <td th:text="#{assignment.no_submissions}"></td>
+                        <td></td>
+                        <td></td>
+                        <td></td>
+                    </tr>
+                    <tr th:if="${submissions != null}" th:each="submission, iter : ${submissions}">
+                        <td th:text="#{submission.name(${submissions.size() - iter.index})}"></td>
+                        <td
+                            th:text="${#temporals.format(submission.submissionTime, 'dd-MM-yyyy HH:mm')}"></td>
+                        <td
+                            th:text="${submission.getMaxGrade() == null} ? #{group.no_grade} : ${@gradeService.getScoreDisplayString(submission.getMaxGrade())}"></td>
+                        <td>
+                            <div class="flex justify-end">
+                                <a
+                                    th:if="${@authorizationService.canDownloadSubmission(submission.id)}"
+                                    class="button p-min"
+                                    data-style="outlined"
+                                    th:href="@{|/submission/${submission.id}/download|}"
+                                    th:text="#{general.download}"></a>
+                                <button
+                                    class="button p-min"
+                                    data-style="outlined"
+                                    th:data-dialog="|feedback-${submission.id}-overlay|"
+                                    th:text="#{submission.feedback}"></button>
+                            </div>
+                        </td>
+                    </tr>
+                </table>
             </div>
-        </div>
-    </div>
-    <div th:each="submission : ${submissions}">
-        <div th:replace="~{submission/feedback :: overlay('')}"></div>
-    </div>
-
-    <div th:replace="~{submission/feedback :: script}"></div>
-    <script>
-        function toggleCollapse(lId, aId) {
-            const list = $(`#${lId}`);
-            list.find(`.list_item:not([data-item="${aId}"])`).addClass("collapsed");
-            list.find(`[data-item="${aId}"]`).toggleClass("collapsed");
-        }
-    </script>
 
-</div>
-
-</body>
-</html>
\ No newline at end of file
+            <div th:replace="~{version/add :: overlay}"></div>
+            <div th:each="version : ${versions}">
+                <div th:replace="~{version/remove :: overlay}"></div>
+            </div>
+            <div th:each="submission : ${submissions}">
+                <div th:replace="~{submission/feedback :: overlay('')}"></div>
+            </div>
+        </main>
+    </body>
+</html>
diff --git a/src/main/resources/templates/container.html b/src/main/resources/templates/container.html
index d26fea96725b62e73caaeff28486374904b4c842..b5d1dd5f90aa6e68fb23f833c72617742af2c9af 100644
--- a/src/main/resources/templates/container.html
+++ b/src/main/resources/templates/container.html
@@ -18,22 +18,25 @@
 
 */-->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
-      layout:decorate="~{layout}">
-
-<body>
-<section class="page" layout:fragment="container">
-    <div layout:replace="~{header}"></div>
-    <div layout:replace="~{announcement/top}"></div>
-    <div layout:fragment="breadcrumbs" class="breadcrumbs"></div>
-    <div class="main-section">
-        <div layout:fragment="sidebar" class="sidebar"></div>
-        <th:block layout:fragment="content" class="content">
-            Content goes here.
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+    layout:decorate="~{layout}">
+    <body>
+        <th:block layout:fragment="container">
+            <header class="header-wrapper">
+                <div layout:replace="~{header :: header}"></div>
+            </header>
+            <div class="content-wrapper">
+                <div class="content">
+                    <div layout:replace="~{announcement/top}"></div>
+                    <main layout:fragment="content">Content goes here.</main>
+                </div>
+            </div>
+            <footer class="footer-wrapper">
+                <div layout:replace="~{footer :: footer}"></div>
+            </footer>
         </th:block>
-    </div>
-    <div layout:fragment="footer"></div>
-</section>
-</body>
-
+    </body>
 </html>
diff --git a/src/main/resources/templates/edition/add_member.html b/src/main/resources/templates/edition/add_member.html
index 20bf9dfab5a44e556181fcee94b1ea9e0783f9e1..1d456c1a030a1dfa63a84687e9d9245c4d08678f 100644
--- a/src/main/resources/templates/edition/add_member.html
+++ b/src/main/resources/templates/edition/add_member.html
@@ -18,48 +18,91 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <dialog
+            th:fragment="staff-overlay"
+            th:if="${@authorizationService.canImportMembersToEdition(edition.id)}"
+            id="add-staff-overlay"
+            class="dialog">
+            <form
+                th:object="${addMember}"
+                th:action="@{|/edition/${edition.id}/members/add|}"
+                method="post"
+                class="flex vertical p-7">
+                <h2 class="underlined font-500" th:text="#{edition.add_staff}"></h2>
 
-<div th:fragment="staff-overlay" th:if="${@authorizationService.canImportMembersToEdition(edition.id)}" id="add-staff-overlay" class="hidden overlay">
-    <form th:object="${addMember}" th:action="@{|/edition/${edition.id}/members/add|}" method="post" class="boxed-content">
-        <h1 class="underlined title" th:text="#{edition.add_staff}"></h1>
-        <div class="form-group">
-            <label for="username" th:text="#{person.username}"></label>
-            <input id="username" th:name="username" th:placeholder="#{person.username.enter}" type="text" class="textfield" required/>
+                <div class="grid col-2 align-center" style="--col-1: minmax(0, 12rem)">
+                    <label for="username" th:text="#{person.username}"></label>
+                    <input
+                        id="username"
+                        th:name="username"
+                        th:placeholder="#{person.username.enter}"
+                        type="text"
+                        class="textfield"
+                        required />
 
-            <label for="role" th:text="#{person.role}"></label>
-            <select id="role" th:name="role" class="selectbox">
-                <option th:each="role : ${T(nl.tudelft.labracore.api.dto.RolePatchDTO.TypeEnum).values()}"
-                        th:value="${role}"
-                        th:unless="${role.name() == 'TEACHER_RO' or role.name() == 'ADMIN' or role.name() == 'BLOCKED' or role.name() == 'STUDENT'}"
-                        th:text="#{|role.${#strings.toLowerCase(role.name())}|}"></option>
-            </select>
-        </div>
-        <div class="form-buttons">
-            <button type=button class="warning text-button" th:text="#{general.cancel}"
-                    onclick="toggleOverlay('add-staff-overlay')"></button>
-            <button type=submit class="text-button" th:text="#{general.save}"></button>
-        </div>
-    </form>
-</div>
+                    <label for="role" th:text="#{person.role}"></label>
+                    <select id="role" th:name="role" class="textfield">
+                        <option
+                            th:each="role : ${T(nl.tudelft.labracore.api.dto.RolePatchDTO.TypeEnum).values()}"
+                            th:value="${role}"
+                            th:unless="${role.name() == 'TEACHER_RO' or role.name() == 'ADMIN' or role.name() == 'BLOCKED' or role.name() == 'STUDENT'}"
+                            th:text="#{|role.${#strings.toLowerCase(role.name())}|}"></option>
+                    </select>
+                </div>
 
-<div th:fragment="student-overlay" th:if="${@authorizationService.canImportMembersToEdition(edition.id)}" id="add-student-overlay" class="hidden overlay">
-    <form th:object="${addMember}" th:action="@{|/edition/${edition.id}/members/add|}" method="post" class="boxed-content">
-        <h1 class="underlined title" th:text="#{edition.add_student}"></h1>
-        <div class="form-group">
-            <input type="hidden" th:name="role" th:value="${T(nl.tudelft.labracore.api.dto.RolePatchDTO.TypeEnum).STUDENT}"/>
+                <div class="flex space-between">
+                    <button
+                        type="button"
+                        class="button p-less"
+                        data-style="outlined"
+                        th:text="#{general.cancel}"
+                        data-cancel></button>
+                    <button type="submit" class="button p-less" th:text="#{general.save}"></button>
+                </div>
+            </form>
+        </dialog>
 
-            <label for="username" th:text="#{person.username}"></label>
-            <input id="username" th:name="username" th:placeholder="#{person.username.enter}" type="text" class="textfield" required/>
-        </div>
-        <div class="form-buttons">
-            <button type=button class="warning text-button" th:text="#{general.cancel}"
-                    onclick="toggleOverlay('add-student-overlay')"></button>
-            <button type=submit class="text-button" th:text="#{general.save}"></button>
-        </div>
-    </form>
-</div>
+        <dialog
+            th:fragment="student-overlay"
+            th:if="${@authorizationService.canImportMembersToEdition(edition.id)}"
+            id="add-student-overlay"
+            class="dialog">
+            <form
+                th:object="${addMember}"
+                th:action="@{|/edition/${edition.id}/members/add|}"
+                method="post"
+                class="flex vertical p-7">
+                <h1 class="underlined font-500" th:text="#{edition.add_student}"></h1>
+                <div class="grid col-2 align-center" style="--col-1: minmax(0, 8rem)">
+                    <input
+                        type="hidden"
+                        th:name="role"
+                        th:value="${T(nl.tudelft.labracore.api.dto.RolePatchDTO.TypeEnum).STUDENT}" />
 
-</body>
-</html>
\ No newline at end of file
+                    <label for="username" th:text="#{person.username}"></label>
+                    <input
+                        id="username"
+                        th:name="username"
+                        th:placeholder="#{person.username.enter}"
+                        type="text"
+                        class="textfield"
+                        required />
+                </div>
+                <div class="flex space-between">
+                    <button
+                        type="button"
+                        class="button p-less"
+                        data-style="outlined"
+                        th:text="#{general.cancel}"
+                        data-cancel></button>
+                    <button type="submit" class="button p-less" th:text="#{general.save}"></button>
+                </div>
+            </form>
+        </dialog>
+    </body>
+</html>
diff --git a/src/main/resources/templates/edition/archive.html b/src/main/resources/templates/edition/archive.html
index ee4fb58cb604892c468bfd02ace223d515d6ca53..e2e855b0ae28d3650dd38af9e8553c4a7ff10bc0 100644
--- a/src/main/resources/templates/edition/archive.html
+++ b/src/main/resources/templates/edition/archive.html
@@ -18,19 +18,39 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <dialog
+            th:fragment="overlay"
+            th:if="${@authorizationService.canArchiveEdition(edition.id)}"
+            id="archive-overlay"
+            class="dialog">
+            <form
+                th:action="@{|/edition/${edition.id}/archive|}"
+                method="post"
+                class="flex vertical p-7">
+                <h2 class="underlined font-500">Archive edition</h2>
 
-<div th:fragment="overlay" th:if="${@authorizationService.canArchiveEdition(edition.id)}" id="archive-overlay" class="hidden confirm overlay">
-    <form th:action="@{|/edition/${edition.id}/archive|}" method="post" class="boxed-content">
-        <p class="confirm_text" th:text="#{edition.archive.confirm(${edition.course.name} + ' - ' + ${edition.name})}"></p>
-        <div class="form-buttons">
-            <button type=button class="warning text-button" th:text="#{general.cancel}"
-                    onclick="toggleOverlay('archive-overlay')"></button>
-            <button type=submit class="text-button" th:text="#{edition.archive}"></button>
-        </div>
-    </form>
-</div>
+                <p
+                    th:text="#{edition.archive.confirm(${edition.course.name} + ' - ' + ${edition.name})}"></p>
 
-</body>
-</html>
\ No newline at end of file
+                <div class="flex space-between">
+                    <button
+                        type="button"
+                        class="button p-less"
+                        data-style="outlined"
+                        data-cancel
+                        th:text="#{general.cancel}"></button>
+                    <button
+                        type="submit"
+                        class="button p-less"
+                        data-type="error"
+                        th:text="#{edition.archive}"></button>
+                </div>
+            </form>
+        </dialog>
+    </body>
+</html>
diff --git a/src/main/resources/templates/edition/assist.html b/src/main/resources/templates/edition/assist.html
new file mode 100644
index 0000000000000000000000000000000000000000..4bff70ebad0065c52f93ee8c74f21042e11801f5
--- /dev/null
+++ b/src/main/resources/templates/edition/assist.html
@@ -0,0 +1,175 @@
+<!--
+
+    Submit
+    Copyright (C) 2020 - Delft University of Technology
+
+    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 <https://www.gnu.org/licenses/>.
+
+-->
+<!DOCTYPE html>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+    layout:decorate="~{container}">
+    <head>
+        <title th:text="|${edition.course.name} - ${edition.name}|"></title>
+    </head>
+
+    <body>
+        <main layout:fragment="content">
+            <div class="breadcrumbs mb-5">
+                <a th:href="@{/edition/enrolled}" th:text="#{edition.editions}"></a>
+                <span>&gt;</span>
+                <a
+                    th:href="@{|/edition/${edition.id}|}"
+                    th:text="|${edition.course.name} - ${edition.name}|"></a>
+            </div>
+
+            <div layout:replace="~{announcement/edition :: top}"></div>
+
+            <div class="flex vertical">
+                <div class="flex space-between align-center">
+                    <h1 class="font-800" th:text="|${edition.course.name} - ${edition.name}|"></h1>
+                    <a
+                        th:if="${@authorizationService.canViewEditionManagerView(edition.id)}"
+                        class="button"
+                        data-style="outlined"
+                        th:href="@{/edition/{id}(id=${edition.id})}">
+                        Exit assistant view
+                    </a>
+                </div>
+
+                <form class="search mb-5" method="get">
+                    <input
+                        aria-label="Search groups or students"
+                        type="search"
+                        name="q"
+                        placeholder="Search for group name, netID, or student name..."
+                        th:value="${param.q}" />
+                    <button type="submit" class="fa-solid fa-search"></button>
+                </form>
+
+                <th:block th:if="${module != null}">
+                    <h2 class="font-500">Modules</h2>
+
+                    <div class="tabs" role="tablist">
+                        <a
+                            role="tab"
+                            th:each="m, iter : ${edition.modules}"
+                            th:href="|?module=${m.id}|"
+                            th:aria-selected="${iter.index == 0 && module == null} or ${module?.id == m.id}"
+                            th:text="${m.name}"></a>
+                    </div>
+
+                    <div class="accordion" id="groups">
+                        <th:block th:each="group : ${groups}">
+                            <div class="accordion__header flex space-between pil-5 pbl-3">
+                                <a
+                                    th:if="${@authorizationService.canViewGroup(group.id)}"
+                                    class="link"
+                                    th:href="@{/group/{id}(id=${group.id})}"
+                                    th:text="${group.name}"></a>
+                                <span
+                                    th:unless="${@authorizationService.canViewGroup(group.id)}"
+                                    th:text="${group.name}"></span>
+                                <button
+                                    th:aria-controls="|group-${group.id}|"
+                                    aria-expanded="false"
+                                    class="fa-solid fa-chevron-down"></button>
+                            </div>
+                            <div th:id="|group-${group.id}|" class="accordion__content">
+                                <span class="pil-5 pbl-3" th:data-fetch="${group.id}">
+                                    Loading...
+                                </span>
+                            </div>
+                        </th:block>
+                        <script>
+                            document.addEventListener("DOMContentLoaded", function () {
+                                function fetchGroup(group) {
+                                    const toReplace =
+                                        group.nextElementSibling.querySelector("[data-fetch]");
+                                    if (toReplace == null) return;
+                                    fetch(`/group/${toReplace.dataset.fetch}/submissions`)
+                                        .then(r => r.text())
+                                        .then(elem => {
+                                            toReplace.parentElement.innerHTML = elem;
+                                            initComponentEvents(
+                                                document.getElementById(
+                                                    `group-${toReplace.dataset.fetch}-submissions`
+                                                )
+                                            );
+                                        })
+                                        .catch(() => window.location.reload());
+                                }
+
+                                const groups = document.getElementById("groups");
+                                groups.querySelectorAll(".accordion__header").forEach(g =>
+                                    g.addEventListener("click", function (event) {
+                                        if (
+                                            ["button", "a"].includes(
+                                                event.target.tagName.toLowerCase()
+                                            ) &&
+                                            !event.target.hasAttribute("aria-controls")
+                                        )
+                                            return;
+                                        fetchGroup(g);
+                                    })
+                                );
+                            });
+                        </script>
+                    </div>
+                </th:block>
+            </div>
+        </main>
+
+        <ul
+            th:fragment="submissions"
+            class="list"
+            role="list"
+            th:id="|group-${group.id}-submissions|">
+            <li
+                th:each="entry : ${group.submissionMap}"
+                th:with="assignment = ${entry.key}, submission = ${entry.value.isEmpty() ? null : entry.value[0]}"
+                class="pil-5 mb-3 flex space-between">
+                <div class="flex align-center gap-3">
+                    <span
+                        th:unless="${@authorizationService.canViewAssignment(assignment.id)}"
+                        th:text="${assignment.name}"></span>
+                    <a
+                        th:if="${@authorizationService.canViewAssignment(assignment.id)}"
+                        class="link"
+                        th:href="@{/assignment/{id}(id=${assignment.id})}"
+                        th:text="${assignment.name}"></a>
+                    <span
+                        th:unless="${submission == null or submission.grades.isEmpty()}"
+                        data-type="info"
+                        class="chip"
+                        th:text="${@gradeService.getScoreDisplayString(submission?.maxGrade)}"></span>
+                </div>
+
+                <th:block th:if="${submission != null}">
+                    <button
+                        class="button p-min"
+                        th:data-dialog="|feedback-${submission.id}-overlay|"
+                        th:text="#{submission.feedback}"></button>
+                </th:block>
+
+                <th:block th:if="${submission != null}">
+                    <th:block th:replace="~{submission/feedback :: overlay('assist')}"></th:block>
+                </th:block>
+            </li>
+        </ul>
+    </body>
+</html>
diff --git a/src/main/resources/templates/edition/contact.html b/src/main/resources/templates/edition/contact.html
index 5f1d37d7c8a4dabdac57f8f7686dcc27fabc6bb7..7eba5f6402a780f55f462879e37c4ed64925a64d 100644
--- a/src/main/resources/templates/edition/contact.html
+++ b/src/main/resources/templates/edition/contact.html
@@ -18,25 +18,34 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
-
-<div th:fragment="overlay" th:if="${@authorizationService.isStudentInEdition(edition.id)}" id="contact-overlay" class="hidden overlay">
-    <div class="boxed-content">
-        <h1 class="underlined title" th:text="#{edition.contact}"></h1>
-        <div class="form-group">
-            <span th:text="#{edition.contact_policy}"></span>
-            <p th:text="${edition.contactPolicy}"></p>
-            <div class="form-separator"></div>
-            <span th:text="#{edition.email}"></span>
-            <span th:text="${edition.email}"></span>
-        </div>
-        <div class="form-buttons">
-            <div></div>
-            <button type=button class="text-button" onclick="toggleOverlay('contact-overlay')" th:text="#{general.close}"></button>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <div
+            th:fragment="overlay"
+            th:if="${@authorizationService.isStudentInEdition(edition.id)}"
+            id="contact-overlay"
+            class="hidden overlay">
+            <div class="boxed-content">
+                <h1 class="underlined title" th:text="#{edition.contact}"></h1>
+                <div class="form-group">
+                    <span th:text="#{edition.contact_policy}"></span>
+                    <p th:text="${edition.contactPolicy}"></p>
+                    <div class="form-separator"></div>
+                    <span th:text="#{edition.email}"></span>
+                    <span th:text="${edition.email}"></span>
+                </div>
+                <div class="form-buttons">
+                    <div></div>
+                    <button
+                        type="button"
+                        class="text-button"
+                        onclick="toggleOverlay('contact-overlay')"
+                        th:text="#{general.close}"></button>
+                </div>
+            </div>
         </div>
-    </div>
-</div>
-
-</body>
-</html>
\ No newline at end of file
+    </body>
+</html>
diff --git a/src/main/resources/templates/edition/create.html b/src/main/resources/templates/edition/create.html
index efc7810a61fe4a6e76dd6bc433fc3aa292d7d634..d08862c244cbc42548806440d922a02ab6477c6b 100644
--- a/src/main/resources/templates/edition/create.html
+++ b/src/main/resources/templates/edition/create.html
@@ -18,67 +18,101 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
-
-<div th:fragment="overlay" th:if="${@authorizationService.isManagerOfAny()}" id="add-overlay" class="hidden overlay">
-    <form th:object="${create}" th:action="@{/edition}" th:method="post" class="boxed-content">
-        <h1 class="underlined title" th:text="#{edition.add}"></h1>
-        <div class="form-group">
-
-            <label for="name" th:text="#{general.name}"></label>
-            <input id="name" th:name="name" th:placeholder="#{general.name.enter}"
-                   type="text" class="textfield" required/>
-
-            <label for="course" th:text="#{edition.course}"></label>
-            <select id="course" th:name="course.id" class="selectbox" required>
-                <option th:each="course : ${manages}" th:value="${course.id}" th:text="${course.name}"></option>
-            </select>
-
-            <label for="cohort" th:text="#{edition.cohort}"></label>
-            <div class="form-vstack">
-                <select id="cohort" th:name="cohort.id" class="selectbox">
-                    <option th:each="cohort : ${cohorts}" th:value="${cohort.id}" th:text="${cohort.name}"></option>
-                </select>
-                <div class="checkbox">
-                    <input id="import-cohort" th:name="importFromCohort" type="checkbox"/>
-                    <label for="import-cohort" th:text="#{edition.import_from_cohort}"></label>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <dialog
+            th:fragment="overlay"
+            th:if="${@authorizationService.isManagerOfAny()}"
+            id="add-overlay"
+            class="dialog"
+            style="--width: 32rem">
+            <form
+                th:object="${create}"
+                th:action="@{/edition}"
+                th:method="post"
+                class="flex vertical p-7">
+                <h1 class="underlined font-500" th:text="#{edition.add}"></h1>
+
+                <div class="grid col-2 gap-3" style="--col-1: minmax(0, 7.5rem)">
+                    <label for="name" th:text="#{general.name}"></label>
+                    <input
+                        id="name"
+                        th:name="name"
+                        th:placeholder="#{general.name.enter}"
+                        type="text"
+                        class="textfield"
+                        required />
+
+                    <label for="course" th:text="#{edition.course}"></label>
+                    <select id="course" th:name="course.id" class="textfield" required>
+                        <option
+                            th:each="course : ${manages}"
+                            th:value="${course.id}"
+                            th:text="${course.name}"></option>
+                    </select>
+
+                    <label for="cohort" th:text="#{edition.cohort}"></label>
+                    <div class="flex vertical gap-1">
+                        <select id="cohort" th:name="cohort.id" class="textfield">
+                            <option
+                                th:each="cohort : ${cohorts}"
+                                th:value="${cohort.id}"
+                                th:text="${cohort.name}"></option>
+                        </select>
+                    </div>
+
+                    <label for="start-date" th:text="#{edition.start_date}"></label>
+                    <input
+                        id="start-date"
+                        th:name="startDate"
+                        th:placeholder="#{edition.start_date.enter}"
+                        type="text"
+                        class="textfield"
+                        pattern="|[0-3]\d-[0-1]\d-\d\d\d\d [0-2]\d:[0-5]\d"
+                        required />
+
+                    <label for="end-date" th:text="#{edition.end_date}"></label>
+                    <input
+                        id="end-date"
+                        th:name="endDate"
+                        th:placeholder="#{edition.end_date.enter}"
+                        type="text"
+                        class="textfield"
+                        pattern="|[0-3]\d-[0-1]\d-\d\d\d\d [0-2]\d:[0-5]\d"
+                        required />
+
+                    <div class="underlined mbl-3" style="grid-column: span 2"></div>
+
+                    <label for="contact" th:text="#{edition.contact_policy}"></label>
+                    <textarea
+                        id="contact"
+                        th:name="contactPolicy"
+                        rows="5"
+                        class="textfield"
+                        th:placeholder="#{edition.contact_policy.enter}"></textarea>
+
+                    <label for="email" th:text="#{edition.email}"></label>
+                    <input
+                        id="email"
+                        th:name="email"
+                        th:placeholder="#{edition.email.enter}"
+                        type="email"
+                        class="textfield" />
                 </div>
-            </div>
-
-            <label for="start-date" th:text="#{edition.start_date}"></label>
-            <input id="start-date" th:name="startDate" th:placeholder="#{edition.start_date.enter}" type="text" class="textfield"
-                   pattern="|[0-3]\d-[0-1]\d-\d\d\d\d [0-2]\d:[0-5]\d" required>
-
-            <label for="end-date" th:text="#{edition.end_date}"></label>
-            <input id="end-date" th:name="endDate" th:placeholder="#{edition.end_date.enter}" type="text" class="textfield"
-                   pattern="|[0-3]\d-[0-1]\d-\d\d\d\d [0-2]\d:[0-5]\d" required>
 
-            <label for="enrolment">Enrolment policy</label>
-            <select id="enrolment" th:name="enrollability" class="selectbox">
-                <option th:each="option : ${T(nl.tudelft.labracore.api.dto.EditionDetailsDTO.EnrollabilityEnum).values()}"
-                        th:text="#{|edition.enrollability.${#strings.toLowerCase(option.name())}|}"
-                        th:value="${option}"></option>
-            </select>
-
-            <div class="form-separator"></div>
-
-            <label for="contact" th:text="#{edition.contact_policy}"></label>
-            <textarea id="contact" th:name="contactPolicy" rows=5 class="textarea"
-                      th:placeholder="#{edition.contact_policy.enter}"></textarea>
-
-            <label for="email" th:text="#{edition.email}"></label>
-            <input id="email" th:name="email" th:placeholder="#{edition.email.enter}"
-                   type="email" class="textfield"/>
-
-        </div>
-        <div class="form-buttons">
-            <button type=button class="warning text-button" th:text="#{general.cancel}"
-                    onclick="toggleOverlay('add-overlay')"></button>
-            <button type=submit class="text-button" th:text="#{general.save}"></button>
-        </div>
-    </form>
-</div>
-
-</body>
-</html>
\ No newline at end of file
+                <div class="flex space-between">
+                    <button
+                        type="button"
+                        class="button p-less"
+                        data-style="outlined"
+                        th:text="#{general.cancel}"
+                        data-cancel></button>
+                    <button type="submit" class="button p-less" th:text="#{general.save}"></button>
+                </div>
+            </form>
+        </dialog>
+    </body>
+</html>
diff --git a/src/main/resources/templates/edition/edit.html b/src/main/resources/templates/edition/edit.html
index 40bcad5dd0c092976fb3850cf3906d640b97de4c..bc63c8f6c6192103ae4cc1f7ba677a2b9b49187f 100644
--- a/src/main/resources/templates/edition/edit.html
+++ b/src/main/resources/templates/edition/edit.html
@@ -18,41 +18,73 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
-
-<div th:fragment="overlay" th:if="${@authorizationService.canEditEdition(edition.id)}" id="edit-overlay" class="hidden overlay">
-    <form th:object="${edition}" th:action="@{|/edition/${edition.id}|}" th:method="patch" class="boxed-content">
-        <h1 class="underlined title" th:text="#{edition.edit}"></h1>
-        <div class="form-group">
-            <label for="name" th:text="#{general.name}"></label>
-            <input id="name" th:name="name" th:placeholder="#{general.name.enter}" th:value="${edition.name}"
-                   type="text" class="textfield" required/>
-            <label for="enrolment">Enrolment policy</label>
-            <select id="enrolment" th:name="enrollability" class="selectbox">
-                <option th:each="option : ${T(nl.tudelft.labracore.api.dto.EditionDetailsDTO.EnrollabilityEnum).values()}"
-                        th:text="#{|edition.enrollability.${#strings.toLowerCase(option.name())}|}"
-                        th:value="${option}"
-                        th:selected="${edition.enrollability.name() == option.name()}"></option>
-            </select>
-            <div class="form-separator"></div>
-            <label for="contact" th:text="#{edition.contact_policy}"></label>
-            <textarea id="contact" th:name="contactPolicy" rows=5 class="textarea"
-                      th:placeholder="#{edition.contact_policy.enter}" th:text="${edition.contactPolicy}"></textarea>
-            <label for="email" th:text="#{edition.email}"></label>
-            <input id="email" th:name="email" th:placeholder="#{edition.email.enter}" th:value="${edition.email}"
-                   type="email" class="textfield"/>
-        </div>
-        <div class="form-buttons form-group">
-            <button class="warning text-button" type="button" onclick="toggleOverlay('archive-overlay')" th:text="#{edition.archive}"></button>
-        </div>
-        <div class="form-buttons">
-            <button type=button class="warning text-button" th:text="#{general.cancel}"
-                    onclick="toggleOverlay('edit-overlay')"></button>
-            <button type=submit class="text-button" th:text="#{general.save}"></button>
-        </div>
-    </form>
-</div>
-
-</body>
-</html>
\ No newline at end of file
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <dialog
+            th:fragment="overlay"
+            th:if="${@authorizationService.canEditEdition(edition.id)}"
+            id="edit-overlay"
+            class="dialog">
+            <form
+                th:object="${edition}"
+                th:action="@{|/edition/${edition.id}|}"
+                th:method="patch"
+                class="flex vertical p-7">
+                <h2 class="underlined font-500" th:text="#{edition.edit}"></h2>
+
+                <div class="grid col-2 gap-3" style="--col-1: minmax(0, 8rem)">
+                    <label for="name" th:text="#{general.name}"></label>
+                    <input
+                        id="name"
+                        th:name="name"
+                        th:placeholder="#{general.name.enter}"
+                        th:value="${edition.name}"
+                        type="text"
+                        class="textfield"
+                        required />
+
+                    <label for="contact" th:text="#{edition.contact_policy}"></label>
+                    <textarea
+                        id="contact"
+                        th:name="contactPolicy"
+                        rows="5"
+                        class="textfield"
+                        th:placeholder="#{edition.contact_policy.enter}"
+                        th:text="${edition.contactPolicy}"></textarea>
+
+                    <label for="email" th:text="#{edition.email}"></label>
+                    <input
+                        id="email"
+                        th:name="email"
+                        th:placeholder="#{edition.email.enter}"
+                        th:value="${edition.email}"
+                        type="email"
+                        class="textfield" />
+                </div>
+
+                <div th:if="${@authorizationService.canArchiveEdition(edition.id)}">
+                    <button
+                        class="button p-less"
+                        type="button"
+                        data-type="error"
+                        data-style="outlined"
+                        data-dialog="archive-overlay"
+                        th:text="#{edition.archive}"></button>
+                </div>
+
+                <div class="flex space-between">
+                    <button
+                        type="button"
+                        class="button p-less"
+                        data-style="outlined"
+                        th:text="#{general.cancel}"
+                        data-cancel></button>
+                    <button type="submit" class="button p-less" th:text="#{general.save}"></button>
+                </div>
+            </form>
+        </dialog>
+    </body>
+</html>
diff --git a/src/main/resources/templates/edition/enrolled.html b/src/main/resources/templates/edition/enrolled.html
index d0ca18a6d80c51513b68e4d3e6d80a0d05e7e915..cac874ec92f5e4270528ba442b51d4eb0ddf6cfc 100644
--- a/src/main/resources/templates/edition/enrolled.html
+++ b/src/main/resources/templates/edition/enrolled.html
@@ -18,51 +18,71 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
-      layout:decorate="~{container}">
-<head>
-    <link rel="stylesheet" type="text/css" th:href="@{/css/editions.css}"/>
-
-    <title th:text="#{edition.enrolled}"></title>
-</head>
-
-<body>
-
-<div layout:fragment="breadcrumbs" class="hidden">
-</div>
-
-<div layout:fragment="sidebar" class="hidden"></div>
-
-<div layout:fragment="content">
-
-    <div class="title-and-search">
-        <h1 class="title" th:text="#{edition.editions}"></h1>
-
-        <form class="search">
-            <input name="q" class="search_field" aria-label="Global search" type="search" th:value="${param.q}"
-                   th:placeholder="#{edition.edition_search}"/>
-            <button class="search_button" type="submit"><span class="fas fa-search"></span></button>
-        </form>
-    </div>
-
-    <div class="editions">
-        <a th:each="role : ${roles}" th:href="@{|/edition/${role.edition.id}|}" class="interactive card edition">
-            <div class="edition_info">
-                <span class="edition_name" th:text="${courses[role.edition.id].name}"></span>
-                <span class="edition_course" th:text="${role.edition.name}"></span>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+    layout:decorate="~{container}">
+    <head>
+        <title th:text="#{edition.enrolled}"></title>
+
+        <style>
+            .edition {
+                text-decoration: none;
+            }
+            .edition:where(:hover, :focus-visible) {
+                background-color: var(--primary-colour);
+                color: var(--on-primary-colour);
+            }
+            .edition:where(:hover, :focus-visible) .chip {
+                border-color: var(--on-primary-colour);
+                color: var(--on-primary-colour);
+            }
+        </style>
+    </head>
+
+    <body>
+        <main layout:fragment="content">
+            <div class="flex space-between align-center mb-5">
+                <h1 class="font-800">Your Courses</h1>
+
+                <form class="search">
+                    <input
+                        name="q"
+                        aria-label="Global search"
+                        type="search"
+                        th:value="${param.q}"
+                        th:placeholder="#{edition.edition_search}" />
+                    <button class="fa-solid fa-search" type="submit"></button>
+                </form>
             </div>
 
-            <span class="edition_role" th:text="${role.edition.isArchived} ? #{edition.archived} : #{|role.${#strings.toLowerCase(role.type.toString())}|}"></span>
-        </a>
-        <button th:if="${@authorizationService.isManagerOfAny()}" onclick="toggleOverlay('add-overlay')" class="interactive card edition_new">
-            <span class="card_icon fas fa-plus-circle"></span>
-            <span th:text="#{edition.add}"></span>
-        </button>
-    </div>
-
-    <div th:replace="~{edition/create :: overlay}"></div>
+            <div class="flex mb-5" th:if="${@authorizationService.isManagerOfAny()}">
+                <button data-dialog="add-overlay" class="button" data-style="outlined">
+                    <span class="fas fa-plus"></span>
+                    <span th:text="#{edition.add}"></span>
+                </button>
+            </div>
 
-</div>
+            <div class="grid auto-fill" style="--col-width: 17.5rem">
+                <a
+                    th:each="role : ${roles}"
+                    th:href="@{|/edition/${role.edition.id}|}"
+                    class="surface flex vertical edition">
+                    <div class="flex space-between align-center">
+                        <div>
+                            <h2 class="font-500" th:text="${courses[role.edition.id].name}"></h2>
+                            <span class="font-300 fw-300" th:text="${role.edition.name}"></span>
+                        </div>
+
+                        <span
+                            class="chip"
+                            th:text="${role.edition.isArchived} ? #{edition.archived} : #{|role.${#strings.toLowerCase(role.type.toString())}|}"></span>
+                    </div>
+                </a>
+            </div>
 
-</body>
-</html>
\ No newline at end of file
+            <div th:replace="~{edition/create :: overlay}"></div>
+        </main>
+    </body>
+</html>
diff --git a/src/main/resources/templates/edition/export_grades.html b/src/main/resources/templates/edition/export_grades.html
index 371be0fb105bf8612810cefafcf1b9283f905f23..25c5868212f080ad16c2a53c09246770095e7427 100644
--- a/src/main/resources/templates/edition/export_grades.html
+++ b/src/main/resources/templates/edition/export_grades.html
@@ -18,47 +18,84 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <dialog
+            th:fragment="overlay"
+            th:if="${@authorizationService.canExportEditionGrades(edition.id)}"
+            id="export-grades-overlay"
+            class="dialog">
+            <form
+                th:object="${exportOptions}"
+                th:action="@{|/edition/${edition.id}/export/grades|}"
+                method="get"
+                class="flex vertical p-7">
+                <h1 class="underlined font-500" th:text="#{edition.export_grades}"></h1>
 
-<div th:fragment="overlay" th:if="${@authorizationService.canExportEditionGrades(edition.id)}" id="export-grades-overlay" class="hidden overlay">
-    <form th:object="${exportOptions}" th:action="@{|/edition/${edition.id}/export/grades|}" method="get" class="boxed-content">
-        <h1 class="underlined title" th:text="#{edition.export_grades}"></h1>
-        <div class="form-group">
+                <div class="grid col-2" style="--col-1: minmax(0, 10rem)">
+                    <label
+                        for="export-edition-grade"
+                        th:text="#{edition.export_grades.edition_grade}"></label>
+                    <div class="checkbox">
+                        <input
+                            id="export-edition-grade"
+                            th:name="exportEditionGrade"
+                            type="checkbox" />
+                        <label
+                            for="export-edition-grade"
+                            th:text="#{edition.export_grades.export_edition_grade}"></label>
+                    </div>
 
-            <label for="export-edition-grade" th:text="#{edition.export_grades.edition_grade}"></label>
-            <div class="checkbox">
-                <input id="export-edition-grade" th:name="exportEditionGrade" type="checkbox"/>
-                <label for="export-edition-grade" th:text="#{edition.export_grades.export_edition_grade}"></label>
-            </div>
+                    <label
+                        for="export-module-grades"
+                        th:text="#{edition.export_grades.module_grades}"></label>
+                    <div class="checkbox">
+                        <input
+                            id="export-module-grades"
+                            th:name="exportModuleGrades"
+                            type="checkbox" />
+                        <label
+                            for="export-module-grades"
+                            th:text="#{edition.export_grades.export_module_grades}"></label>
+                    </div>
 
-            <label for="export-module-grades" th:text="#{edition.export_grades.module_grades}"></label>
-            <div class="checkbox">
-                <input id="export-module-grades" th:name="exportModuleGrades" type="checkbox"/>
-                <label for="export-module-grades" th:text="#{edition.export_grades.export_module_grades}"></label>
-            </div>
+                    <label
+                        for="export-assignment-grades"
+                        th:text="#{edition.export_grades.assignment_grades}"></label>
+                    <div class="checkbox">
+                        <input
+                            id="export-assignment-grades"
+                            th:name="exportAssignmentGrades"
+                            type="checkbox" />
+                        <label
+                            for="export-assignment-grades"
+                            th:text="#{edition.export_grades.export_assignment_grades}"></label>
+                    </div>
 
-            <label for="export-assignment-grades" th:text="#{edition.export_grades.assignment_grades}"></label>
-            <div class="checkbox">
-                <input id="export-assignment-grades" th:name="exportAssignmentGrades" type="checkbox"/>
-                <label for="export-assignment-grades" th:text="#{edition.export_grades.export_assignment_grades}"></label>
-            </div>
+                    <label
+                        for="script-grade-policy"
+                        th:text="#{edition.export_grades.script_grade_policy}"></label>
+                    <select id="script-grade-policy" class="textfield" th:name="scriptGradePolicy">
+                        <option
+                            th:each="policy : ${T(nl.tudelft.submit.enums.ScriptGradePolicy).values()}"
+                            th:value="${policy}"
+                            th:text="#{|edition.export_grades.script_grade_policy.${#strings.toLowerCase(policy.name())}|}"></option>
+                    </select>
+                </div>
 
-            <label for="script-grade-policy" th:text="#{edition.export_grades.script_grade_policy}"></label>
-            <select id="script-grade-policy" class="selectbox" th:name="scriptGradePolicy">
-                <option th:each="policy : ${T(nl.tudelft.submit.enums.ScriptGradePolicy).values()}"
-                        th:value="${policy}"
-                        th:text="#{|edition.export_grades.script_grade_policy.${#strings.toLowerCase(policy.name())}|}"></option>
-            </select>
-
-        </div>
-        <div class="form-buttons">
-            <button type=button class="warning text-button" th:text="#{general.cancel}"
-                    onclick="toggleOverlay('export-grades-overlay')"></button>
-            <button type=submit class="text-button" th:text="#{general.save}"></button>
-        </div>
-    </form>
-</div>
-
-</body>
-</html>
\ No newline at end of file
+                <div class="flex space-between">
+                    <button
+                        type="button"
+                        class="button p-less"
+                        data-style="outlined"
+                        th:text="#{general.cancel}"
+                        data-cancel></button>
+                    <button type="submit" class="button p-less">Export</button>
+                </div>
+            </form>
+        </dialog>
+    </body>
+</html>
diff --git a/src/main/resources/templates/edition/grading.html b/src/main/resources/templates/edition/grading.html
index 4802fd4d36a8ae5b0cff5279d7305036344e9f26..95125d4cec06f1e829d5b0fad27b9780fdbc4233 100644
--- a/src/main/resources/templates/edition/grading.html
+++ b/src/main/resources/templates/edition/grading.html
@@ -18,94 +18,147 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <dialog
+            th:fragment="overlay"
+            th:if="${@authorizationService.canEditEditionGrading(edition.id)}"
+            id="grading-overlay"
+            class="dialog">
+            <script>
+                function testFormula() {
+                    let formula = document.getElementById("formula").value;
+                    let type = document.getElementById("grade-type").value;
+                    let scores = {};
+                    for (let score of document.getElementById("test-fields").children) {
+                        scores[score.id.substring(5)] = score.value;
+                    }
+                    let testData = {
+                        formula: formula,
+                        type: type,
+                        scores: scores,
+                    };
+                    $.ajax({
+                        type: "POST",
+                        url: "/grade/test",
+                        data: JSON.stringify(testData),
+                        success: res => (document.getElementById("test-result").value = res.result),
+                        dataType: "json",
+                        contentType: "application/json",
+                    });
+                }
+            </script>
+            <form
+                th:object="${grading}"
+                th:action="@{|/edition/${edition.id}/grading|}"
+                class="flex vertical p-7"
+                th:method="patch">
+                <h2 class="underlined font-500" th:text="#{edition.grading_formula}"></h2>
 
-<div th:fragment="overlay" th:if="${@authorizationService.canEditEditionGrading(edition.id)}" id="grading-overlay" class="hidden overlay">
-    <script>
-        function testFormula() {
-            let formula = document.getElementById("formula").value;
-            let type = document.getElementById("grade-type").value;
-            let scores = {}
-            for (let score of document.getElementById("test-fields").children) {
-                scores[score.id.substring(5)] = score.value;
-            }
-            let testData = {
-                "formula": formula,
-                "type": type,
-                "scores": scores
-            };
-            $.ajax({
-                type: "POST",
-                url: "/grade/test",
-                data: JSON.stringify(testData),
-                success: res => document.getElementById("test-result").value = res.result,
-                dataType: "json",
-                contentType: "application/json"
-            });
-        }
-    </script>
-    <form th:object="${grading}" th:action="@{|/edition/${edition.id}/grading|}" class="boxed-content" th:method="patch">
-        <h1 class="underlined title" th:text="#{edition.grading_formula}"></h1>
-        <div class="form-group">
+                <div class="grid col-2 gap-3 align-center" style="--col-1: minmax(0, 8rem)">
+                    <div>
+                        <label for="formula" th:text="#{grading.formula}"></label>
+                        <div>
+                            <a
+                                th:href="@{/grade/help}"
+                                class="link"
+                                target="_blank"
+                                th:text="#{general.help}"></a>
+                        </div>
+                    </div>
+                    <div class="flex vertical gap-1">
+                        <input
+                            id="formula"
+                            th:name="formula"
+                            class="textfield"
+                            type="text"
+                            th:value="${edition.gradingFormula?.formula}"
+                            th:placeholder="#{grading.formula.enter}" />
+                        <div class="font-200 pl-1">
+                            <span th:text="|#{grading.available_scores}:|"></span>
+                            <span
+                                th:text="${#strings.listJoin(edition.modules.![@gradeService.generateVariableName(#this.name)], ', ')}"></span>
+                        </div>
+                    </div>
 
-            <div>
-                <label for="formula" th:text="#{grading.formula}"></label>
-                <div><a th:href="@{/grade/help}" class="smaller text-button" th:text="#{general.help}"></a></div>
-            </div>
-            <input id="formula" th:name="formula" class="textfield" type="text"
-                   th:value="${edition.gradingFormula?.formula}"
-                   th:placeholder="#{grading.formula.enter}"/>
+                    <label for="grade-type" th:text="#{general.type}"></label>
+                    <select id="grade-type" th:name="type" class="textfield">
+                        <option
+                            th:each="type : ${T(nl.tudelft.labracore.lib.api.GradeScheme).values()}"
+                            th:selected="${type.name() == edition.gradingFormula?.type?.name()}"
+                            th:value="${type}"
+                            th:text="#{|grading.type.${#strings.toLowerCase(type)}|}"></option>
+                    </select>
 
-            <span th:text="#{grading.available_scores}"></span>
-            <div><span class="smaller" th:each="module, iter : ${edition.modules}" th:with="name = ${@gradeService.generateVariableName(module.name)}"
-                       th:text="${iter.count == edition.modules.size()} ? ${name} : ${name + ', '}"></span></div>
+                    <div class="underlined" style="grid-column: span 2"></div>
 
-            <label for="grade-type" th:text="#{general.type}"></label>
-            <select id="grade-type" th:name="type" class="selectbox">
-                <option th:each="type : ${T(nl.tudelft.labracore.lib.api.GradeScheme).values()}"
-                        th:selected="${type.name() == edition.gradingFormula?.type?.name()}"
-                        th:value="${type}" th:text="#{|grading.type.${#strings.toLowerCase(type)}|}"></option>
-            </select>
+                    <div>
+                        <span th:text="#{grading.test_formula}"></span>
+                        <div class="tooltip">
+                            <span class="tooltip__control fa-solid fa-question"></span>
+                            <div role="tooltip" style="white-space: initial; min-width: 30rem">
+                                <p th:text="#{grading.test_formula.help}"></p>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="flex align-center">
+                        <div th:id="test-fields" class="flex vertical gap-3">
+                            <input
+                                th:each="module : ${edition.modules}"
+                                th:with="name = ${@gradeService.generateVariableName(module.name)}"
+                                class="textfield"
+                                type="text"
+                                th:id="|test-${name}|"
+                                th:aria-label="|${module.name} test score|"
+                                th:placeholder="${module.name}" />
+                        </div>
+                        <span>=</span>
+                        <input
+                            id="test-result"
+                            aria-label="Formula test result"
+                            class="textfield"
+                            type="text"
+                            readonly />
+                        <button
+                            class="button p-less"
+                            data-style="outlined"
+                            type="button"
+                            th:text="#{grading.calculate}"
+                            onclick="testFormula()"></button>
+                    </div>
 
-            <div class="form-separator"></div>
+                    <div class="underlined" style="grid-column: span 2"></div>
 
-            <div>
-                <span th:text="#{grading.test_formula}"></span>
-                <div class="tooltip">
-                    <span class="tooltip_icon">?</span>
-                    <p class="tooltip_text" th:text="#{grading.test_formula.help}"></p>
+                    <div>
+                        <label for="passing-grade" th:text="#{grading.passing_grade}"></label>
+                        <div class="tooltip">
+                            <span class="tooltip__control fa-solid fa-question"></span>
+                            <div role="tooltip">
+                                <p class="tooltip_text" th:text="#{grading.passing_grade.help}"></p>
+                            </div>
+                        </div>
+                    </div>
+                    <input
+                        id="passing-grade"
+                        th:name="passingScore"
+                        class="textfield"
+                        type="text"
+                        th:value="${edition.passingScore}"
+                        th:placeholder="#{grading.passing_grade.enter}" />
                 </div>
-            </div>
-            <div class="form-hstack">
-                <div th:id="test-fields" class="form-vstack">
-                    <input th:each="module : ${edition.modules}" th:with="name = ${@gradeService.generateVariableName(module.name)}" class="textfield" type="text"
-                           th:id="|test-${name}|" th:aria-label="|${module.name} test score|" th:placeholder="${module.name}"/>
+                <div class="flex space-between">
+                    <button
+                        type="button"
+                        class="button p-less"
+                        data-style="outlined"
+                        th:text="#{general.cancel}"
+                        data-cancel></button>
+                    <button type="submit" class="button p-less" th:text="#{general.save}"></button>
                 </div>
-                <span>=</span>
-                <input id="test-result" aria-label="Formula test result" class="textfield" type="text" readonly/>
-                <button class="text-button" type="button" th:text="#{grading.calculate}" onclick="testFormula()"></button>
-            </div>
-
-            <div class="form-separator"></div>
-
-            <div>
-                <label for="passing-grade" th:text="#{grading.passing_grade}"></label>
-                <div class="tooltip">
-                    <span class="tooltip_icon">?</span>
-                    <p class="tooltip_text" th:text="#{grading.passing_grade.help}"></p>
-                </div>
-            </div>
-            <input id="passing-grade" th:name="passingScore" class="textfield" type="text" th:value="${edition.passingScore}"
-                   th:placeholder="#{grading.passing_grade.enter}"/>
-        </div>
-        <div class="form-buttons">
-            <button type=button class="warning text-button" th:text="#{general.cancel}"
-                    onclick="toggleOverlay('grading-overlay')"></button>
-            <button type=submit class="text-button" th:text="#{general.save}"></button>
-        </div>
-    </form>
-</div>
-
-</body>
-</html>
\ No newline at end of file
+            </form>
+        </dialog>
+    </body>
+</html>
diff --git a/src/main/resources/templates/edition/hide.html b/src/main/resources/templates/edition/hide.html
index 3df0e43e76ac6a6b905a2f5fd32535da8671e320..1604d477e32879d2ac8c92a79bbcf8a8682b710e 100644
--- a/src/main/resources/templates/edition/hide.html
+++ b/src/main/resources/templates/edition/hide.html
@@ -18,19 +18,40 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <dialog
+            th:fragment="overlay"
+            th:if="${@authorizationService.canEditEdition(edition.id)}"
+            id="hide-overlay"
+            class="dialog">
+            <form
+                th:action="@{|/edition/${edition.id}/toggle-hide|}"
+                method="post"
+                class="flex vertical p-7">
+                <h2
+                    class="font-500 underlined"
+                    th:text="${edition.hidden} ? 'Show edition' : 'Hide edition'"></h2>
 
-<div th:fragment="overlay" th:if="${@authorizationService.canEditEdition(edition.id)}" id="hide-overlay" class="hidden confirm overlay">
-    <form th:action="@{|/edition/${edition.id}/toggle-hide|}" method="post" class="boxed-content">
-        <p class="confirm_text" th:text="${edition.hidden} ? #{edition.show.confirm(${edition.course.name} + ' - ' + ${edition.name})} : #{edition.hide.confirm(${edition.course.name} + ' - ' + ${edition.name})}"></p>
-        <div class="form-buttons">
-            <button type=button class="warning text-button" th:text="#{general.cancel}"
-                    onclick="toggleOverlay('hide-overlay')"></button>
-            <button type=submit class="text-button" th:text="${edition.hidden} ? #{edition.show} : #{edition.hide}"></button>
-        </div>
-    </form>
-</div>
+                <p
+                    th:text="${edition.hidden} ? #{edition.show.confirm(${edition.course.name} + ' - ' + ${edition.name})} : #{edition.hide.confirm(${edition.course.name} + ' - ' + ${edition.name})}"></p>
 
-</body>
-</html>
\ No newline at end of file
+                <div class="flex space-between">
+                    <button
+                        type="button"
+                        class="button p-less"
+                        data-style="outlined"
+                        th:text="#{general.cancel}"
+                        data-cancel></button>
+                    <button
+                        type="submit"
+                        class="button p-less"
+                        th:text="${edition.hidden} ? #{edition.show} : #{edition.hide}"></button>
+                </div>
+            </form>
+        </dialog>
+    </body>
+</html>
diff --git a/src/main/resources/templates/edition/open.html b/src/main/resources/templates/edition/open.html
index 23e208c792464f7030ff5abf008ed9baa510a056..c0e87402c161eb817d583fbc4aafd73d95f0592f 100644
--- a/src/main/resources/templates/edition/open.html
+++ b/src/main/resources/templates/edition/open.html
@@ -18,52 +18,53 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
-      layout:decorate="~{container}">
-<head>
-    <title th:text="#{edition.enrol_page}"></title>
-</head>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+    layout:decorate="~{container}">
+    <head>
+        <title th:text="#{edition.enrol_page}"></title>
+    </head>
 
-<body>
+    <body>
+        <main layout:fragment="content" class="flex vertical">
+            <div class="flex space-between align-center">
+                <h1 class="font-800">Catalog</h1>
 
-<div layout:fragment="breadcrumbs">
-    <a th:href="@{/edition/enrolled}" th:text="#{edition.editions}"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{/edition/open}" th:text="#{edition.enrol_page}"></a>
-</div>
+                <form class="search">
+                    <input
+                        name="q"
+                        aria-label="Global search"
+                        type="search"
+                        th:value="${param.q}"
+                        th:placeholder="#{edition.edition_search}" />
+                    <button class="fas fa-search" type="submit"></button>
+                </form>
+            </div>
 
-<div layout:fragment="sidebar" class="hidden"></div>
-
-<div layout:fragment="content">
-
-    <div class="title-and-search">
-        <h1 class="title" th:text="#{edition.enrol}"></h1>
-
-        <form class="search">
-            <input name="q" class="search_field" aria-label="Global search" type="search" th:value="${param.q}"
-                   th:placeholder="#{edition.edition_search}"/>
-            <button class="search_button" type="submit"><span class="fas fa-search"></span></button>
-        </form>
-    </div>
-
-    <table class="table">
-        <tr class="table_header">
-            <th th:text="#{edition.edition}"></th>
-            <th th:text="#{edition.code}"></th>
-            <th th:text="#{edition.status}"></th>
-            <th></th>
-        </tr>
-        <tr th:each="edition : ${editions}">
-            <td th:text="|${edition.course.name} - ${edition.name}|"></td>
-            <td th:text="${edition.course.code}"></td>
-            <td th:text="#{edition.status.open}"></td>
-            <td><form th:action="@{|/edition/${edition.id}/join|}" method="post">
-                <button class="text-button" type=submit th:text="#{edition.enrol}"></button>
-            </form></td>
-        </tr>
-    </table>
-
-</div>
-
-</body>
-</html>
\ No newline at end of file
+            <table class="table" data-style="surface">
+                <tr class="table__header">
+                    <th th:text="#{edition.edition}"></th>
+                    <th th:text="#{edition.code}"></th>
+                    <th></th>
+                </tr>
+                <tr th:if="${editions.isEmpty()}">
+                    <td colspan="2">There are currently no open courses editions.</td>
+                </tr>
+                <tr th:each="edition : ${editions}">
+                    <td th:text="|${edition.course.name} - ${edition.name}|"></td>
+                    <td th:text="${edition.course.code}"></td>
+                    <td class="flex justify-end">
+                        <form th:action="@{|/edition/${edition.id}/join|}" method="post">
+                            <button
+                                class="button p-min"
+                                type="submit"
+                                th:text="#{edition.enrol}"></button>
+                        </form>
+                    </td>
+                </tr>
+            </table>
+        </main>
+    </body>
+</html>
diff --git a/src/main/resources/templates/edition/staff.html b/src/main/resources/templates/edition/staff.html
index a4f57b7d64edcf38e92a41cfa918c97761b53b99..40d867e04fd9ddac783dc42cb4cc65b9e3679bbb 100644
--- a/src/main/resources/templates/edition/staff.html
+++ b/src/main/resources/templates/edition/staff.html
@@ -18,74 +18,125 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
-      layout:decorate="~{container}">
-<head>
-    <title th:text="#{edition.staff}"></title>
-</head>
-
-<body>
-
-<div layout:fragment="breadcrumbs">
-    <a th:href="@{/edition/enrolled}" th:text="#{edition.editions}"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{|/edition/${edition.id}|}" th:text="|${edition.course.name} - ${edition.name}|"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{|/edition/${edition.id}/staff|}" th:text="#{edition.staff}"></a>
-</div>
-
-<div layout:fragment="sidebar" class="hidden"></div>
-
-<div layout:fragment="content">
-
-    <div th:replace="~{announcement/edition :: top}"></div>
-
-    <div class="title-and-search">
-        <h1 class="title" th:text="#{edition.staff}"></h1>
-
-        <form class="search">
-            <input name="q" class="search_field" aria-label="Student search" type="search" th:value="${param.q}"
-                   th:placeholder="#{edition.staff_search}"/>
-            <button class="search_button" type="submit"><span class="fas fa-search"></span></button>
-        </form>
-    </div>
-
-    <table class="table">
-        <tr class="table_header">
-            <th th:text="#{general.name}"></th>
-            <th><div th:if="${@authorizationService.canImportMembersToEdition(edition.id)}" class="table_actions">
-                <button type="button" class="text-button" onclick="toggleOverlay('import-overlay')" th:text="#{general.import}"></button>
-                <button class="text-button" th:text="#{edition.add_staff}" onclick="toggleOverlay('add-staff-overlay')"></button>
-            </div></th>
-        </tr>
-        <tr th:each="role : ${roles}">
-            <td th:text="${role.person.username}"></td>
-            <td><div th:if="${@authorizationService.canEditRoleTo(role.id, 'STUDENT')}" class="table_actions">
-                <form th:id="|change-role-${role.person.id}|" th:object="${rolePatch}" th:action="@{|/role/${role.person.id}/${edition.id}|}" th:method="patch">
-                    <select aria-label="Change role" th:name="type" class="selectbox" th:onchange="|toggleOverlay('role-${role.person.id}-overlay')|">
-                        <option th:each="roleType : ${T(nl.tudelft.labracore.api.dto.RolePersonDetailsDTO.TypeEnum).values()}"
-                                th:unless="${roleType.name() == 'ADMIN' || roleType.name() == 'TEACHER_RO' || roleType.name() == 'BLOCKED'}"
-                                th:value="${roleType}" th:text="#{|role.${#strings.toLowerCase(roleType.name())}|}"
-                                th:disabled="${not @authorizationService.canEditRoleTo(role.id, roleType.name())}"
-                                th:selected="${roleType.name() == role.type.name()}"></option>
-                    </select>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+    layout:decorate="~{container}">
+    <head>
+        <title th:text="#{edition.staff}"></title>
+    </head>
+
+    <body>
+        <div layout:fragment="content">
+            <th:block layout:replace="~{edition/top :: top}"></th:block>
+
+            <div class="flex vertical">
+                <form class="search">
+                    <input
+                        name="q"
+                        aria-label="Student search"
+                        type="search"
+                        th:value="${param.q}"
+                        th:placeholder="#{edition.staff_search}" />
+                    <button class="fas fa-search" type="submit"></button>
                 </form>
-                <a th:if="${@authorizationService.canViewPerson(role.person.id)}" th:href="@{|/person/${role.person.id}|}" class="text-button" type=button th:text="#{general.details}"></a>
-                <button th:if="${@authorizationService.canEditRoleNote(role.id)}" class="text-button" type=button th:text="#{general.note}" th:onclick="|toggleOverlay('note-${role.person.id}-overlay')|"></button>
-                <button th:if="${@authorizationService.canRemovePersonFromEdition(edition.id, role.person.id)}" class="warning icon-button" type=button th:onclick="|toggleOverlay('remove-${role.person.id}-overlay')|"><span class="far fa-trash-alt"></span></button>
-            </div></td>
-        </tr>
-    </table>
-
-    <div th:each="role : ${roles}">
-        <div th:replace="~{person/change_role :: overlay}"></div>
-        <div th:replace="~{person/note :: overlay('staff')}"></div>
-        <div th:replace="~{person/remove :: overlay('staff')}"></div>
-    </div>
-    <div th:replace="~{person/import :: overlay('staff')}"></div>
-    <div th:replace="~{edition/add_member :: staff-overlay}"></div>
-
-</div>
 
-</body>
-</html>
\ No newline at end of file
+                <table class="table" data-style="surface">
+                    <tr class="table__header">
+                        <th class="fit-content" th:text="#{general.name}"></th>
+                        <th class="fit-content" th:text="#{person.role}"></th>
+
+                        <th>
+                            <div
+                                th:if="${@authorizationService.canImportMembersToEdition(edition.id)}"
+                                class="flex gap-3 justify-end">
+                                <button
+                                    type="button"
+                                    data-style="outlined"
+                                    class="button p-min"
+                                    data-dialog="import-overlay"
+                                    th:text="#{general.import}"></button>
+                                <button
+                                    class="button p-min"
+                                    data-style="outlined"
+                                    th:text="#{edition.add_staff}"
+                                    data-dialog="add-staff-overlay"></button>
+                            </div>
+                        </th>
+                    </tr>
+
+                    <tr th:each="role : ${roles}">
+                        <td class="fit-content">
+                            <a
+                                th:if="${@authorizationService.canViewPerson(role.person.id)}"
+                                th:href="@{|/person/${role.person.id}|}"
+                                class="link"
+                                style="white-space: nowrap"
+                                th:text="${role.person.displayName}"></a>
+                            <span
+                                style="white-space: nowrap"
+                                th:unless="${@authorizationService.canViewPerson(role.person.id)}"
+                                th:text="${role.person.displayName}"></span>
+                        </td>
+                        <td class="fit-content">
+                            <span
+                                th:if="${@authorizationService.isStudentInEdition(edition.id)}"
+                                th:text="#{|role.${role.type.name().toLowerCase()}|}"></span>
+                            <form
+                                th:unless="${@authorizationService.isStudentInEdition(edition.id)}"
+                                th:id="|change-role-${role.person.id}|"
+                                th:object="${rolePatch}"
+                                th:action="@{|/role/${role.person.id}/${edition.id}|}"
+                                th:method="patch">
+                                <select
+                                    aria-label="Change role"
+                                    th:name="type"
+                                    class="textfield"
+                                    data-style="variant"
+                                    th:onchange="|document.getElementById('role-${role.person.id}-overlay').showModal()|">
+                                    <option
+                                        th:each="roleType : ${T(nl.tudelft.labracore.api.dto.RolePersonDetailsDTO.TypeEnum).values()}"
+                                        th:unless="${roleType.name() == 'ADMIN' || roleType.name() == 'TEACHER_RO' || roleType.name() == 'BLOCKED'}"
+                                        th:value="${roleType}"
+                                        th:text="#{|role.${#strings.toLowerCase(roleType.name())}|}"
+                                        th:disabled="${not @authorizationService.canEditRoleTo(role.id, roleType.name())}"
+                                        th:selected="${roleType.name() == role.type.name()}"></option>
+                                </select>
+                            </form>
+                        </td>
+                        <td>
+                            <div
+                                th:if="${@authorizationService.canEditRoleTo(role.id, 'STUDENT')}"
+                                class="flex gap-3 justify-end">
+                                <button
+                                    th:if="${@authorizationService.canEditRoleNote(role.id)}"
+                                    class="button p-min"
+                                    type="button"
+                                    data-style="outlined"
+                                    th:text="#{general.note}"
+                                    th:data-dialog="|note-${role.person.id}-overlay|"></button>
+                                <button
+                                    th:if="${@authorizationService.canRemovePersonFromEdition(edition.id, role.person.id)}"
+                                    class="button p-min"
+                                    data-style="outlined"
+                                    type="button"
+                                    data-type="error"
+                                    th:data-dialog="|remove-${role.person.id}-overlay|"
+                                    th:text="#{general.remove}"></button>
+                            </div>
+                        </td>
+                    </tr>
+                </table>
+            </div>
+
+            <div th:each="role : ${roles}">
+                <div th:replace="~{person/change_role :: overlay}"></div>
+                <div th:replace="~{person/note :: overlay('staff')}"></div>
+                <div th:replace="~{person/remove :: overlay('staff')}"></div>
+            </div>
+            <div th:replace="~{person/import :: overlay('staff')}"></div>
+            <div th:replace="~{edition/add_member :: staff-overlay}"></div>
+        </div>
+    </body>
+</html>
diff --git a/src/main/resources/templates/edition/statistics.html b/src/main/resources/templates/edition/statistics.html
index b245e93ad1ad8ac27898a2dc6542f76ec7afd8dd..227cb1f02b14a03a1ef82f95f4e1fa66ed71717a 100644
--- a/src/main/resources/templates/edition/statistics.html
+++ b/src/main/resources/templates/edition/statistics.html
@@ -18,66 +18,46 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
-      layout:decorate="~{container}">
-<head>
-    <script src="/webjars/chartjs/Chart.min.js"></script>
-
-    <title th:text="#{general.statistics}"></title>
-</head>
-
-<body>
-
-<div layout:fragment="breadcrumbs">
-    <a th:href="@{/edition/enrolled}" th:text="#{edition.editions}"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{|/edition/${edition.id}|}" th:text="|${edition.course.name} - ${edition.name}|"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{|/edition/${edition.id}/statistics|}" th:text="#{general.statistics}"></a>
-</div>
-
-<div layout:fragment="sidebar">
-    <a th:if="${@authorizationService.canViewEdition(edition.id)}" class="sidebar_item" th:href="@{|/edition/${edition.id}|}" th:text="#{general.details}"></a>
-    <a th:if="${@authorizationService.canEditAnnouncementsForEdition(edition.id)}" class="sidebar_item" th:href="@{|/edition/${edition.id}/announcements|}" th:text="#{announcement.announcements}"></a>
-    <a th:if="${@authorizationService.canViewEditionStudents(edition.id)}" class="sidebar_item" th:href="@{|/edition/${edition.id}/students|}" th:text="#{edition.students}"></a>
-    <a th:if="${@authorizationService.canViewEditionStaff(edition.id)}" class="sidebar_item" th:href="@{|/edition/${edition.id}/staff|}" th:text="#{edition.staff}"></a>
-    <a th:if="${@authorizationService.canViewEditionStatistics(edition.id)}" class="sidebar_item" th:href="@{|/edition/${edition.id}/statistics|}" th:text="#{general.statistics}"></a>
-</div>
-
-<div layout:fragment="content">
-
-    <div th:replace="~{announcement/edition :: top}"></div>
-
-    <h1 class="title" th:text="|${edition.course.name} - ${edition.name}|"></h1>
-
-    <div class="tabs">
-        <a th:href="@{|/edition/${edition.id}/statistics|}" class="active tab" th:text="#{general.statistics}"></a>
-        <a th:href="@{|/edition/${edition.id}/statistics-table|}" class="tab" th:text="#{general.table}"></a>
-    </div>
-
-    <div class="info_row">
-        <div class="card_stats card">
-            <div class="card_stats_row">
-                <span th:text="#{statistics.total_students}"></span>
-                <span th:text="${statistics.totalStudents}"></span>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+    layout:decorate="~{container}">
+    <head>
+        <script src="/webjars/chartjs/Chart.min.js"></script>
+
+        <title th:text="#{general.statistics}"></title>
+    </head>
+
+    <body>
+        <div layout:fragment="content">
+            <div layout:replace="~{edition/top :: top}"></div>
+
+            <div class="surface">
+                <div class="flex vertical gap-3">
+                    <div class="underlined flex space-between">
+                        <span th:text="#{statistics.total_students}"></span>
+                        <span th:text="${statistics.totalStudents}"></span>
+                    </div>
+                    <div
+                        th:if="${statistics.averageScore != null}"
+                        class="underlined flex space-between pil-3">
+                        <span th:text="#{statistics.average_score}"></span>
+                        <span th:text="${statistics.averageScore}"></span>
+                    </div>
+                    <div
+                        th:if="${statistics.medianScore != null}"
+                        class="underlined flex space-between pil-3">
+                        <span th:text="#{statistics.median_score}"></span>
+                        <span th:text="${statistics.medianScore}"></span>
+                    </div>
+                </div>
             </div>
-            <div th:if="${statistics.averageScore != null}" class="card_stats_row">
-                <span th:text="#{statistics.average_score}"></span>
-                <span th:text="${statistics.averageScore}"></span>
-            </div>
-            <div th:if="${statistics.medianScore != null}" class="card_stats_row">
-                <span th:text="#{statistics.median_score}"></span>
-                <span th:text="${statistics.medianScore}"></span>
+
+            <div class="grid col-2">
+                <div th:replace="~{statistics/view :: pass-fail}"></div>
+                <div th:replace="~{statistics/view :: score-distribution}"></div>
             </div>
         </div>
-    </div>
-
-    <div class="info_row">
-        <div th:replace="~{statistics/view :: pass-fail}"></div>
-        <div th:replace="~{statistics/view :: score-distribution}"></div>
-    </div>
-
-</div>
-
-</body>
+    </body>
 </html>
diff --git a/src/main/resources/templates/edition/statistics_table.html b/src/main/resources/templates/edition/statistics_table.html
index 7cd7a3a72684eeb2fe7181fbfdd70adb3140a7c9..c4307396f8ce7d60f1691e03a94e5b6880666488 100644
--- a/src/main/resources/templates/edition/statistics_table.html
+++ b/src/main/resources/templates/edition/statistics_table.html
@@ -18,69 +18,113 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
-      layout:decorate="~{container}">
-<head>
-    <title th:text="#{general.statistics}"></title>
-</head>
-
-<body>
-
-<div layout:fragment="breadcrumbs">
-    <a th:href="@{/edition/enrolled}" th:text="#{edition.editions}"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{|/edition/${edition.id}|}" th:text="|${edition.course.name} - ${edition.name}|"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{|/edition/${edition.id}/statistics|}" th:text="#{general.statistics}"></a>
-</div>
-
-<div layout:fragment="sidebar">
-    <a th:if="${@authorizationService.canViewEdition(edition.id)}" class="sidebar_item" th:href="@{|/edition/${edition.id}|}" th:text="#{general.details}"></a>
-    <a th:if="${@authorizationService.canEditAnnouncementsForEdition(edition.id)}" class="sidebar_item" th:href="@{|/edition/${edition.id}/announcements|}" th:text="#{announcement.announcements}"></a>
-    <a th:if="${@authorizationService.canViewEditionStudents(edition.id)}" class="sidebar_item" th:href="@{|/edition/${edition.id}/students|}" th:text="#{edition.students}"></a>
-    <a th:if="${@authorizationService.canViewEditionStaff(edition.id)}" class="sidebar_item" th:href="@{|/edition/${edition.id}/staff|}" th:text="#{edition.staff}"></a>
-    <a th:if="${@authorizationService.canViewEditionStatistics(edition.id)}" class="sidebar_item" th:href="@{|/edition/${edition.id}/statistics|}" th:text="#{general.statistics}"></a>
-</div>
-
-<div layout:fragment="content">
-
-    <div th:replace="~{announcement/edition :: top}"></div>
-
-    <h1 class="title" th:text="|${edition.course.name} - ${edition.name}|"></h1>
-
-    <div class="tabs">
-        <a th:href="@{|/edition/${edition.id}/statistics|}" class="tab" th:text="#{general.statistics}"></a>
-        <a th:href="@{|/edition/${edition.id}/statistics-table|}" class="active tab" th:text="#{general.table}"></a>
-    </div>
-
-    <div class="title-and-search">
-        <h2 class="subtitle"></h2>
-
-        <form id="filter-form" class="title-and-search_elements">
-            <select name="status" aria-label="Filter on status" class="selectbox" onchange="$('#filter-form').submit()">
-                <option value="all" th:text="#{grading.any_status}"></option>
-                <option th:each="status : ${T(nl.tudelft.submit.enums.GradeStatistics).values()}" th:selected="${#strings.toString(param.status) == status.name()}" th:value="${status.name()}" th:text="#{|grading.${#strings.toLowerCase(status.name())}|}"></option>
-            </select>
-            <div class="search">
-                <input name="q" class="search_field" th:value="${param.q}" aria-label="Student search" type="search" th:placeholder="#{edition.student_search}"/>
-                <button class="search_button" type="submit"><span class="fas fa-search"></span></button>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+    layout:decorate="~{container}">
+    <head>
+        <title th:text="#{general.statistics}"></title>
+    </head>
+
+    <body>
+        <div layout:fragment="breadcrumbs">
+            <a th:href="@{/edition/enrolled}" th:text="#{edition.editions}"></a>
+            <span>&nbsp;&gt;&nbsp;</span>
+            <a
+                th:href="@{|/edition/${edition.id}|}"
+                th:text="|${edition.course.name} - ${edition.name}|"></a>
+            <span>&nbsp;&gt;&nbsp;</span>
+            <a th:href="@{|/edition/${edition.id}/statistics|}" th:text="#{general.statistics}"></a>
+        </div>
+
+        <div layout:fragment="sidebar">
+            <a
+                th:if="${@authorizationService.canViewEdition(edition.id)}"
+                class="sidebar_item"
+                th:href="@{|/edition/${edition.id}|}"
+                th:text="#{general.details}"></a>
+            <a
+                th:if="${@authorizationService.canEditAnnouncementsForEdition(edition.id)}"
+                class="sidebar_item"
+                th:href="@{|/edition/${edition.id}/announcements|}"
+                th:text="#{announcement.announcements}"></a>
+            <a
+                th:if="${@authorizationService.canViewEditionStudents(edition.id)}"
+                class="sidebar_item"
+                th:href="@{|/edition/${edition.id}/students|}"
+                th:text="#{edition.students}"></a>
+            <a
+                th:if="${@authorizationService.canViewEditionStaff(edition.id)}"
+                class="sidebar_item"
+                th:href="@{|/edition/${edition.id}/staff|}"
+                th:text="#{edition.staff}"></a>
+            <a
+                th:if="${@authorizationService.canViewEditionStatistics(edition.id)}"
+                class="sidebar_item"
+                th:href="@{|/edition/${edition.id}/statistics|}"
+                th:text="#{general.statistics}"></a>
+        </div>
+
+        <div layout:fragment="content">
+            <div th:replace="~{announcement/edition :: top}"></div>
+
+            <h1 class="title" th:text="|${edition.course.name} - ${edition.name}|"></h1>
+
+            <div class="tabs">
+                <a
+                    th:href="@{|/edition/${edition.id}/statistics|}"
+                    class="tab"
+                    th:text="#{general.statistics}"></a>
+                <a
+                    th:href="@{|/edition/${edition.id}/statistics-table|}"
+                    class="active tab"
+                    th:text="#{general.table}"></a>
             </div>
-        </form>
-    </div>
 
-    <table class="table">
-        <tr class="table_header">
-            <th th:text="#{role.student}"></th>
-            <th th:text="#{grading.grade}"></th>
-            <th></th>
-        </tr>
-        <tr th:each="row : ${data}">
-            <td th:text="${row.person.username}"></td>
-            <td th:text="${row.grade == null} ? #{grading.no_grade} : ${@gradeService.getScoreDisplayString(row.grade)}"></td>
-        </tr>
-    </table>
-
-</div>
+            <div class="title-and-search">
+                <h2 class="subtitle"></h2>
+
+                <form id="filter-form" class="title-and-search_elements">
+                    <select
+                        name="status"
+                        aria-label="Filter on status"
+                        class="selectbox"
+                        onchange="$('#filter-form').submit()">
+                        <option value="all" th:text="#{grading.any_status}"></option>
+                        <option
+                            th:each="status : ${T(nl.tudelft.submit.enums.GradeStatistics).values()}"
+                            th:selected="${#strings.toString(param.status) == status.name()}"
+                            th:value="${status.name()}"
+                            th:text="#{|grading.${#strings.toLowerCase(status.name())}|}"></option>
+                    </select>
+                    <div class="search">
+                        <input
+                            name="q"
+                            class="search_field"
+                            th:value="${param.q}"
+                            aria-label="Student search"
+                            type="search"
+                            th:placeholder="#{edition.student_search}" />
+                        <button class="search_button" type="submit">
+                            <span class="fas fa-search"></span>
+                        </button>
+                    </div>
+                </form>
+            </div>
 
-</body>
-</html>
\ No newline at end of file
+            <table class="table">
+                <tr class="table_header">
+                    <th th:text="#{role.student}"></th>
+                    <th th:text="#{grading.grade}"></th>
+                    <th></th>
+                </tr>
+                <tr th:each="row : ${data}">
+                    <td th:text="${row.person.username}"></td>
+                    <td
+                        th:text="${row.grade == null} ? #{grading.no_grade} : ${@gradeService.getScoreDisplayString(row.grade)}"></td>
+                </tr>
+            </table>
+        </div>
+    </body>
+</html>
diff --git a/src/main/resources/templates/edition/student.html b/src/main/resources/templates/edition/student.html
new file mode 100644
index 0000000000000000000000000000000000000000..0a2970709586da0aa9f44b5fcc91b0debe4ea258
--- /dev/null
+++ b/src/main/resources/templates/edition/student.html
@@ -0,0 +1,205 @@
+<!--
+
+    Submit
+    Copyright (C) 2020 - Delft University of Technology
+
+    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 <https://www.gnu.org/licenses/>.
+
+-->
+<!DOCTYPE html>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+    layout:decorate="~{container}">
+    <head>
+        <title th:text="|${edition.course.name} - ${edition.name}|"></title>
+    </head>
+
+    <body>
+        <main layout:fragment="content">
+            <div class="breadcrumbs mb-5">
+                <a th:href="@{/edition/enrolled}" th:text="#{edition.editions}"></a>
+                <span>&gt;</span>
+                <a
+                    th:href="@{|/edition/${edition.id}|}"
+                    th:text="|${edition.course.name} - ${edition.name}|"></a>
+            </div>
+
+            <div layout:replace="~{announcement/edition :: top}"></div>
+
+            <div class="flex vertical">
+                <div class="flex space-between align-center">
+                    <h1 class="font-800" th:text="|${edition.course.name} - ${edition.name}|"></h1>
+                    <a
+                        th:if="${@authorizationService.canViewEditionManagerView(edition.id)}"
+                        class="button"
+                        data-style="outlined"
+                        th:href="@{/edition/{id}(id=${edition.id})}">
+                        Exit assistant view
+                    </a>
+                </div>
+
+                <div class="grid col-2 | md:col-1">
+                    <div class="surface flex vertical">
+                        <h2 class="font-400">Upcoming deadlines</h2>
+                        <div class="flex vertical gap-3">
+                            <div class="underlined" th:if="${upcomingDeadlines.isEmpty()}">
+                                No upcoming deadlines
+                            </div>
+                            <div
+                                class="underlined flex space-between"
+                                th:each="deadline : ${upcomingDeadlines}">
+                                <span
+                                    th:text="|${deadline.module().name} - ${deadline.assignment().name}|"></span>
+                                <span
+                                    th:text="${#temporals.format(deadline.assignment().deadline, 'd MMMM HH:mm')}"></span>
+                            </div>
+                        </div>
+                    </div>
+
+                    <div class="surface flex vertical">
+                        <h2 class="font-400">Grade</h2>
+                        <span th:if="${editionGrade == null}">No grade</span>
+                        <span
+                            th:unless="${editionGrade == null}"
+                            th:text="${@gradeService.getScoreDisplayString(editionGrade)}"></span>
+                    </div>
+                </div>
+
+                <th:block th:if="${module != null}">
+                    <div class="tabs" role="tablist">
+                        <a
+                            role="tab"
+                            th:each="m, iter : ${edition.modules}"
+                            th:href="|?module=${m.id}|"
+                            th:aria-selected="${iter.index == 0 && module == null} or ${module?.id == m.id}"
+                            th:text="${m.name}"></a>
+                    </div>
+
+                    <div class="grid col-2 | md:col-1">
+                        <div class="surface flex vertical">
+                            <th:block th:if="${group == null}">
+                                <h2 class="font-500">Group</h2>
+                                <span>No group</span>
+                            </th:block>
+                            <th:block th:unless="${group == null}">
+                                <h2
+                                    class="font-500"
+                                    th:unless="${group == null}"
+                                    th:text="|Group: ${group.name}|"></h2>
+                                <span
+                                    th:text="${#strings.listJoin(group.members.![person.displayName], ', ')}"></span>
+                            </th:block>
+                        </div>
+                        <div class="surface flex vertical">
+                            <h2 class="font-500">Grade</h2>
+                            <span th:if="${moduleGrade == null}">No grade</span>
+                            <span
+                                th:unless="${moduleGrade == null}"
+                                th:text="${@gradeService.getScoreDisplayString(grade)}"></span>
+                        </div>
+                    </div>
+
+                    <div th:if="${group == null}">
+                        <h2 class="font-500">Groups</h2>
+                        <ul class="divided list" role="list">
+                            <li class="pil-5 pbl-3 flex space-between" th:each="group : ${groups}">
+                                <span
+                                    th:text="|${group.name} (${group.memberUsernames.size()}/${group.capacity})|"></span>
+                                <form th:action="@{/group/{id}/join(id=${group.id})}" method="post">
+                                    <button
+                                        class="button p-min"
+                                        th:disabled="not ${@authorizationService.canJoinGroup(group.id)} or ${group.memberUsernames.size() >= group.capacity}">
+                                        Join
+                                    </button>
+                                </form>
+                            </li>
+                        </ul>
+                    </div>
+
+                    <div class="accordion" th:unless="${group == null}">
+                        <th:block
+                            th:each="entry : ${submissionMap}"
+                            th:with="assignment = ${entry.key}, submissions = ${entry.value}">
+                            <div class="accordion__header">
+                                <div class="flex space-between pil-5 pbl-3">
+                                    <div class="flex align-center gap-3">
+                                        <span th:text="${assignment.name}"></span>
+                                        <span
+                                            th:if="not ${submissions.isEmpty()} and ${submissions[0].maxGrade != null}"
+                                            class="chip"
+                                            th:text="${@gradeService.getScoreDisplayString(submissions[0].maxGrade)}"></span>
+                                    </div>
+                                    <div class="flex">
+                                        <div class="flex gap-3 font-300">
+                                            <button
+                                                class="button p-min"
+                                                th:data-dialog="|submit-${assignment.id}-overlay|">
+                                                Submit
+                                            </button>
+                                        </div>
+                                        <button
+                                            class="fa-solid fa-chevron-down"
+                                            th:aria-controls="|assignment-${assignment.id}|"></button>
+                                    </div>
+                                </div>
+                            </div>
+                            <ul
+                                th:id="|assignment-${assignment.id}|"
+                                role="list"
+                                class="accordion__content">
+                                <li class="pil-5 mb-3" th:if="${submissions.isEmpty()}">
+                                    No submissions
+                                </li>
+                                <li
+                                    th:each="submission, iter : ${submissions}"
+                                    class="pil-5 mb-3 flex space-between">
+                                    <div class="flex align-center gap-3">
+                                        <span
+                                            th:text="|Submission ${submissions.size() - iter.index} (${#temporals.format(submission.submissionTime, 'd MMMM HH:mm')})|"></span>
+                                        <span
+                                            th:if="${submission.maxGrade != null}"
+                                            class="chip"
+                                            th:text="${@gradeService.getScoreDisplayString(submission.maxGrade)}"></span>
+                                    </div>
+                                    <div class="flex gap-3">
+                                        <a
+                                            class="button p-min"
+                                            data-style="outlined"
+                                            th:href="@{/submission/{id}/download(id=${submission.id})}">
+                                            Download
+                                        </a>
+                                        <button
+                                            class="button p-min"
+                                            data-style="outlined"
+                                            th:data-dialog="|feedback-${submission.id}-overlay|">
+                                            Feedback
+                                        </button>
+                                    </div>
+
+                                    <th:block
+                                        th:replace="~{submission/feedback :: overlay('student')}"></th:block>
+                                </li>
+                            </ul>
+
+                            <th:block
+                                th:replace="~{assignment/submit :: overlay('student')}"></th:block>
+                        </th:block>
+                    </div>
+                </th:block>
+            </div>
+        </main>
+    </body>
+</html>
diff --git a/src/main/resources/templates/edition/student_actions.html b/src/main/resources/templates/edition/student_actions.html
index aba07f3558a5174ab677377a697f73815c8810d7..599b2b2c2c05b87c7db0ce9326a15195b5838af6 100644
--- a/src/main/resources/templates/edition/student_actions.html
+++ b/src/main/resources/templates/edition/student_actions.html
@@ -18,72 +18,99 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
-      layout:decorate="~{container}">
-<head>
-    <title th:text="#{edition.students}"></title>
-</head>
-
-<body>
-
-<div layout:fragment="breadcrumbs">
-    <a th:href="@{/edition/enrolled}" th:text="#{edition.editions}"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{|/edition/${edition.id}|}" th:text="|${edition.course.name} - ${edition.name}|"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:if="${@authorizationService.canViewEditionOverview(edition.id)}" th:href="@{|/edition/${edition.id}/students|}" th:text="#{edition.student_overview}"></a>
-    <a th:unless="${@authorizationService.canViewEditionOverview(edition.id)}" th:href="@{|/edition/${edition.id}/students/actions|}" th:text="#{edition.student_overview}"></a>
-</div>
-
-<div layout:fragment="sidebar" class="hidden"></div>
-
-<div layout:fragment="content">
-
-    <div th:replace="~{announcement/edition :: top}"></div>
-
-    <div class="title-and-search">
-        <h1 class="title" th:text="#{edition.students}"></h1>
-
-        <form class="search">
-            <input name="q" class="search_field" aria-label="Student search" type="search" th:value="${param.q}"
-                   th:placeholder="#{edition.student_search}"/>
-            <button class="search_button" type="submit"><span class="fas fa-search"></span></button>
-        </form>
-    </div>
-
-    <div th:if="${@authorizationService.canViewEditionOverview(edition.id)}" class="tabs">
-        <a th:href="@{|/edition/${edition.id}/students|}" class="tab" th:text="#{general.overview}">Overview</a>
-        <a th:href="@{|/edition/${edition.id}/students/actions|}" class="active tab" th:text="#{general.actions}">Actions</a>
-    </div>
-
-    <table class="table">
-        <tr class="table_header">
-            <th th:text="#{general.name}"></th>
-            <th><div th:if="${@authorizationService.canImportMembersToEdition(edition.id)}" class="table_actions">
-                <button type="button" class="text-button" onclick="toggleOverlay('import-overlay')" th:text="#{general.import}"></button>
-                <button class="text-button" th:text="#{edition.add_students}" onclick="toggleOverlay('add-student-overlay')"></button>
-            </div></th>
-        </tr>
-        <tr th:each="role : ${roles}">
-            <td th:text="${role.person.username}"></td>
-            <td><div class="table_actions">
-                <button th:if="${@authorizationService.canPromoteStudent(edition.id, role.person.id)}" class="text-button" type=button th:onclick="|toggleOverlay('ta-${role.person.id}-overlay')|" th:text="#{person.make_ta}"></button>
-                <a th:if="${@authorizationService.canViewPerson(role.person.id)}" th:href="@{|/person/${role.person.id}|}" class="text-button" type=button th:text="#{general.details}"></a>
-                <button th:if="${@authorizationService.canEditRoleNote(role.id)}" class="text-button" type=button th:text="#{general.note}" th:onclick="|toggleOverlay('note-${role.person.id}-overlay')|"></button>
-                <button th:if="${@authorizationService.canRemovePersonFromEdition(edition.id, role.person.id)}" class="warning icon-button" type=button th:onclick="|toggleOverlay('remove-${role.person.id}-overlay')|"><span class="far fa-trash-alt"></span></button>
-            </div></td>
-        </tr>
-    </table>
-
-    <div th:each="role : ${roles}">
-        <div th:replace="~{person/make_ta :: overlay}"></div>
-        <div th:replace="~{person/note :: overlay('students')}"></div>
-        <div th:replace="~{person/remove :: overlay('students')}"></div>
-    </div>
-    <div th:replace="~{person/import :: overlay('students')}"></div>
-    <div th:replace="~{edition/add_member :: student-overlay}"></div>
-
-</div>
-
-</body>
-</html>
\ No newline at end of file
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+    layout:decorate="~{container}">
+    <head>
+        <title th:text="#{edition.students}"></title>
+    </head>
+
+    <body>
+        <main layout:fragment="content">
+            <th:block layout:replace="~{edition/top :: top}"></th:block>
+
+            <div class="flex vertical">
+                <form class="search">
+                    <input
+                        name="q"
+                        class="search_field"
+                        aria-label="Student search"
+                        type="search"
+                        th:value="${param.q}"
+                        th:placeholder="#{edition.student_search}" />
+                    <button class="fas fa-search" type="submit"></button>
+                </form>
+
+                <table class="table" data-style="surface">
+                    <tr class="table__header">
+                        <th th:text="#{general.name}"></th>
+                        <th>
+                            <div
+                                th:if="${@authorizationService.canImportMembersToEdition(edition.id)}"
+                                class="flex gap-3 justify-end">
+                                <button
+                                    type="button"
+                                    class="button p-min"
+                                    data-style="outlined"
+                                    data-dialog="import-overlay"
+                                    th:text="#{general.import}"></button>
+                                <button
+                                    class="button p-min"
+                                    data-style="outlined"
+                                    th:text="#{edition.add_students}"
+                                    data-dialog="add-student-overlay"></button>
+                            </div>
+                        </th>
+                    </tr>
+                    <tr th:each="role : ${roles}">
+                        <td>
+                            <a
+                                th:if="${@authorizationService.canViewPerson(role.person.id)}"
+                                th:href="@{|/person/${role.person.id}|}"
+                                class="link"
+                                th:text="${role.person.displayName}"></a>
+                            <span
+                                th:unless="${@authorizationService.canViewPerson(role.person.id)}"
+                                th:text="${role.person.displayName}"></span>
+                        </td>
+                        <td>
+                            <div class="flex gap-3 justify-end">
+                                <button
+                                    th:if="${@authorizationService.canPromoteStudent(edition.id, role.person.id)}"
+                                    class="button p-min"
+                                    data-style="outlined"
+                                    type="button"
+                                    th:data-dialog="|ta-${role.person.id}-overlay|"
+                                    th:text="#{person.make_ta}"></button>
+                                <button
+                                    th:if="${@authorizationService.canEditRoleNote(role.id)}"
+                                    class="button p-min"
+                                    data-style="outlined"
+                                    th:text="#{general.note}"
+                                    th:data-dialog="|note-${role.person.id}-overlay|"></button>
+                                <button
+                                    th:if="${@authorizationService.canRemovePersonFromEdition(edition.id, role.person.id)}"
+                                    class="button p-min"
+                                    data-style="outlined"
+                                    data-type="error"
+                                    th:data-dialog="|remove-${role.person.id}-overlay|">
+                                    Remove
+                                </button>
+                            </div>
+                        </td>
+                    </tr>
+                </table>
+            </div>
+
+            <div th:each="role : ${roles}">
+                <div th:replace="~{person/make_ta :: overlay}"></div>
+                <div th:replace="~{person/note :: overlay('students')}"></div>
+                <div th:replace="~{person/remove :: overlay('students')}"></div>
+            </div>
+            <div th:replace="~{person/import :: overlay('students')}"></div>
+            <div th:replace="~{edition/add_member :: student-overlay}"></div>
+        </main>
+    </body>
+</html>
diff --git a/src/main/resources/templates/edition/students.html b/src/main/resources/templates/edition/students.html
index d44eadaf05611fd57eb34fd55b9b6a1a5ebb58c9..77efbab486752b62c7c9a80b9f4c527c98785626 100644
--- a/src/main/resources/templates/edition/students.html
+++ b/src/main/resources/templates/edition/students.html
@@ -18,72 +18,65 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
-      layout:decorate="~{container}">
-<head>
-    <link rel="stylesheet" type="text/css" th:href="@{/css/progress.css}"/>
-
-    <title th:text="#{edition.students}"></title>
-</head>
-
-<body>
-
-<div layout:fragment="breadcrumbs">
-    <a th:href="@{/edition/enrolled}" th:text="#{edition.editions}"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{|/edition/${edition.id}|}" th:text="|${edition.course.name} - ${edition.name}|"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{|/edition/${edition.id}/students|}" th:text="#{edition.student_overview}"></a>
-</div>
-
-<div layout:fragment="sidebar" class="hidden"></div>
-
-<div layout:fragment="content">
-
-    <div th:replace="~{announcement/edition :: top}"></div>
-
-    <div class="title-and-search">
-        <h1 class="title" th:text="#{edition.students}"></h1>
-
-        <form class="search">
-            <input name="q" class="search_field" aria-label="Student search" type="search" th:value="${param.q}"
-                   th:placeholder="#{edition.student_search}"/>
-            <button class="search_button" type="submit"><span class="fas fa-search"></span></button>
-        </form>
-    </div>
-
-    <div class="tabs">
-        <a th:href="@{|/edition/${edition.id}/students|}" class="active tab" th:text="#{general.overview}">Overview</a>
-        <a th:href="@{|/edition/${edition.id}/students/actions|}" class="tab" th:text="#{general.actions}">Actions</a>
-    </div>
-
-    <table class="progress table">
-        <tr>
-            <td class="table_info" th:text="#{edition.student_overview.click}"></td>
-            <td th:each="module : ${edition.modules}">
-                <a class="text-button" th:href="@{|/module/${module.id}/progress|}" th:text="${module.name}"></a>
-            </td>
-        </tr>
-        <tr class="table_header">
-            <th th:text="#{general.name}"></th>
-            <td th:each="module : ${edition.modules}">
-                <span th:text="#{edition.student_overview.percentage(${progress[module.id]})}"></span>
-            </td>
-        </tr>
-        <tr th:each="row : ${data}">
-            <td>
-                <a th:if="${@authorizationService.canViewPerson(row.person.id)}" class="text-button" th:href="@{|/person/${row.person.id}|}" th:text="${row.person.username}"></a>
-                <span th:unless="${@authorizationService.canViewPerson(row.person.id)}" th:text="${row.person.username}"></span>
-            </td>
-            <td th:each="entry : ${row.moduleGrades}" th:with="grade = ${entry.value?.moduleGrade}"
-                th:classappend="${grade == null} ? '' :
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+    layout:decorate="~{container}">
+    <head>
+        <title th:text="#{edition.students}"></title>
+    </head>
+
+    <body>
+        <main layout:fragment="content">
+            <th:block layout:replace="~{edition/top :: top}"></th:block>
+
+            <div class="flex vertical">
+                <form class="search">
+                    <input
+                        name="q"
+                        class="search_field"
+                        aria-label="Student search"
+                        type="search"
+                        th:value="${param.q}"
+                        th:placeholder="#{edition.student_search}" />
+                    <button class="fas fa-search" type="submit"></button>
+                </form>
+
+                <table class="table">
+                    <tr class="table__header">
+                        <th th:text="#{general.name}"></th>
+                        <td th:each="module : ${edition.modules}">
+                            <a
+                                class="link"
+                                th:href="@{|/module/${module.id}/progress|}"
+                                th:text="${module.name}"></a>
+                            <span
+                                th:text="|(#{edition.student_overview.percentage(${progress[module.id]})})|"></span>
+                        </td>
+                    </tr>
+                    <tr th:each="row : ${data}">
+                        <td>
+                            <a
+                                th:if="${@authorizationService.canViewPerson(row.person.id)}"
+                                class="link"
+                                th:href="@{|/person/${row.person.id}|}"
+                                th:text="${row.person.displayName}"></a>
+                            <span
+                                th:unless="${@authorizationService.canViewPerson(row.person.id)}"
+                                th:text="${row.person.displayName}"></span>
+                        </td>
+                        <td
+                            th:each="entry : ${row.moduleGrades}"
+                            th:with="grade = ${entry.value?.moduleGrade}"
+                            th:classappend="${grade == null} ? '' :
                                 (${@gradeService.isPassing(grade, edition.id) == false} ? 'fail' : 'pass')">
-                <span th:text="${grade == null} ? #{grading.no_grade} : ${@gradeService.getScoreDisplayString(grade)}"></span>
-            </td>
-        </tr>
-    </table>
-
-</div>
-
-</body>
-</html>
\ No newline at end of file
+                            <span
+                                th:text="${grade == null} ? #{grading.no_grade} : ${@gradeService.getScoreDisplayString(grade)}"></span>
+                        </td>
+                    </tr>
+                </table>
+            </div>
+        </main>
+    </body>
+</html>
diff --git a/src/main/resources/templates/edition/top.html b/src/main/resources/templates/edition/top.html
new file mode 100644
index 0000000000000000000000000000000000000000..dd3bd914eccd1fa5623f6a1811ee8fce2622f44e
--- /dev/null
+++ b/src/main/resources/templates/edition/top.html
@@ -0,0 +1,142 @@
+<!--
+
+    Submit
+    Copyright (C) 2020 - Delft University of Technology
+
+    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 <https://www.gnu.org/licenses/>.
+
+-->
+<!DOCTYPE html>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <div layout:fragment="top">
+            <div class="breadcrumbs mb-5">
+                <a th:href="@{/edition/enrolled}" th:text="#{edition.editions}"></a>
+                <span>&gt;</span>
+                <a
+                    th:href="@{|/edition/${edition.id}|}"
+                    th:text="|${edition.course.name} - ${edition.name}|"></a>
+            </div>
+
+            <div layout:replace="~{announcement/edition :: top}"></div>
+
+            <div
+                th:if="${edition.hidden and @authorizationService.canEditEdition(edition.id)}"
+                class="banner mb-3"
+                data-type="warning">
+                <p th:text="#{edition.hide.info}"></p>
+            </div>
+
+            <h1 class="font-800 mb-3" th:text="|${edition.course.name} - ${edition.name}|"></h1>
+
+            <div class="flex gap-3 wrap mb-5 | sm:vertical">
+                <a
+                    th:if="${@authorizationService.canAssistEdition(edition.id)}"
+                    class="button"
+                    data-style="outlined"
+                    th:href="@{/edition/{id}/assist(id=${edition.id})}">
+                    Enter assistant view
+                </a>
+                <button
+                    th:if="${@authorizationService.canEditEdition(edition.id)}"
+                    class="button"
+                    data-style="outlined"
+                    data-dialog="edit-overlay">
+                    Edit edition
+                </button>
+                <button
+                    th:if="${@authorizationService.canEditEdition(edition.id)}"
+                    class="button"
+                    data-style="outlined"
+                    data-dialog="hide-overlay"
+                    th:text="${edition.hidden} ? 'Show edition' : 'Hide edition'"></button>
+                <button
+                    th:if="${@authorizationService.canEditEditionGrading(edition.id)}"
+                    class="button"
+                    data-style="outlined"
+                    data-dialog="grading-overlay">
+                    Grading
+                </button>
+                <a
+                    th:if="${@authorizationService.canExportEditionSubmissions(edition.id)}"
+                    class="button"
+                    data-style="outlined"
+                    th:href="@{/edition/{id}/export/submissions(id=${edition.id})}">
+                    Export submissions
+                </a>
+                <button
+                    th:if="${@authorizationService.canExportEditionGrades(edition.id)}"
+                    class="button"
+                    data-style="outlined"
+                    data-dialog="export-grades-overlay">
+                    Export grades
+                </button>
+            </div>
+
+            <div class="tabs mb-5" role="tablist">
+                <a
+                    role="tab"
+                    th:if="${@authorizationService.canViewEdition(edition.id)}"
+                    th:aria-selected="${#request.requestURI.matches('.*edition/[0-9]+')}"
+                    th:href="@{/edition/{id}(id=${edition.id})}">
+                    Overview
+                </a>
+                <a
+                    role="tab"
+                    th:if="${@authorizationService.canViewEditionStaff(edition.id)}"
+                    th:aria-selected="${#request.requestURI.matches('.*/staff')}"
+                    th:href="@{/edition/{id}/staff(id=${edition.id})}">
+                    Staff
+                </a>
+                <a
+                    role="tab"
+                    th:if="${@authorizationService.canViewEditionStudents(edition.id)}"
+                    th:aria-selected="${#request.requestURI.matches('.*/students/actions')}"
+                    th:href="@{/edition/{id}/students/actions(id=${edition.id})}">
+                    Students
+                </a>
+                <a
+                    role="tab"
+                    th:if="${@authorizationService.canViewEditionOverview(edition.id)}"
+                    th:aria-selected="${#request.requestURI.matches('.*/students')}"
+                    th:href="@{/edition/{id}/students(id=${edition.id})}">
+                    Progress
+                </a>
+                <a
+                    role="tab"
+                    th:if="${@authorizationService.canViewEditionStatistics(edition.id)}"
+                    th:aria-selected="${#request.requestURI.matches('.*/statistics')}"
+                    th:href="@{/edition/{id}/statistics(id=${edition.id})}">
+                    Statistics
+                </a>
+                <a
+                    role="tab"
+                    th:if="${@authorizationService.canEditAnnouncementsForEdition(edition.id)}"
+                    th:aria-selected="${#request.requestURI.matches('.*/announcements')}"
+                    th:href="@{/edition/{id}/announcements(id=${edition.id})}">
+                    Announcements
+                </a>
+            </div>
+
+            <th:block th:replace="~{edition/archive :: overlay}"></th:block>
+            <th:block th:replace="~{edition/edit :: overlay}"></th:block>
+            <th:block th:replace="~{edition/hide :: overlay}"></th:block>
+            <th:block th:replace="~{edition/grading :: overlay}"></th:block>
+            <th:block th:replace="~{edition/export_grades :: overlay}"></th:block>
+        </div>
+    </body>
+</html>
diff --git a/src/main/resources/templates/edition/view.html b/src/main/resources/templates/edition/view.html
index 57964f4fecf62e644ede7364c511d095dfcc5b67..44704367ef625a6de1985f6fdef0f45292aa5950 100644
--- a/src/main/resources/templates/edition/view.html
+++ b/src/main/resources/templates/edition/view.html
@@ -18,134 +18,83 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
-      layout:decorate="~{container}">
-<head>
-    <link rel="stylesheet" type="text/css" th:href="@{/css/edition.css}"/>
-
-    <title th:text="|${edition.course.name} - ${edition.name}|"></title>
-
-    <script th:inline="javascript">
-        let flag = false
-        function toModule(id) {
-            /*<![CDATA[*/
-            let location = "/module/" + id + /*[[${@authorizationService.isStudentInEdition(edition.id)} ? '/groups' : '/']]*/ '/';
-            /*]]>*/
-            if (!flag) window.location = location;
-            flag = false;
-        }
-        function toggleCollapse(id) {
-            flag = true;
-            document.getElementById("module-" + id).classList.toggle("collapsed");
-        }
-        function toggleHoverInside(id) {
-            document.getElementById("module-" + id).classList.toggle("hover-inside");
-        }
-    </script>
-</head>
-
-<body>
-
-<div layout:fragment="breadcrumbs">
-    <a th:href="@{/edition/enrolled}" th:text="#{edition.editions}"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{|/edition/${edition.id}|}" th:text="|${edition.course.name} - ${edition.name}|"></a>
-</div>
-
-<div layout:fragment="sidebar">
-    <a class="sidebar_item" th:href="@{|/edition/${edition.id}|}" th:text="#{general.details}"></a>
-    <div class="sidebar_group">
-        <button th:if="${@authorizationService.isStudentInEdition(edition.id)}" class="sidebar_item" th:text="#{edition.contact}" onclick="toggleOverlay('contact-overlay')"></button>
-        <button th:if="${@authorizationService.canEditEdition(edition.id)}" class="sidebar_item" th:text="#{edition.edit}" onclick="toggleOverlay('edit-overlay')"></button>
-        <button th:if="${@authorizationService.canEditEditionGrading(edition.id)}" class="sidebar_item" th:text="#{grading.grading}" onclick="toggleOverlay('grading-overlay')"></button>
-        <button th:if="${@authorizationService.canExportEditionGrades(edition.id)}" class="sidebar_item" th:text="#{edition.export_grades}" onclick="toggleOverlay('export-grades-overlay')"></button>
-        <a th:if="${@authorizationService.canExportEditionSubmissions(edition.id)}" class="sidebar_item" th:text="#{edition.export_submissions}" th:href="@{|/edition/${edition.id}/export/submissions|}"></a>
-        <button th:if="${@authorizationService.canEditEdition(edition.id)}" class="sidebar_item" th:text="${edition.hidden} ? #{edition.show} : #{edition.hide}" onclick="toggleOverlay('hide-overlay')"></button>
-    </div>
-    <a th:if="${@authorizationService.canEditAnnouncementsForEdition(edition.id)}" class="sidebar_item" th:href="@{|/edition/${edition.id}/announcements|}" th:text="#{announcement.announcements}"></a>
-    <a th:if="${@authorizationService.canViewEditionOverview(edition.id)}" class="sidebar_item" th:href="@{|/edition/${edition.id}/students|}" th:text="#{edition.students}"></a>
-    <a th:if="${not @authorizationService.canViewEditionOverview(edition.id) and @authorizationService.canViewEditionStudents(edition.id)}" class="sidebar_item" th:href="@{|/edition/${edition.id}/students/actions|}" th:text="#{edition.students}"></a>
-    <a th:if="${@authorizationService.canViewEditionStaff(edition.id)}" class="sidebar_item" th:href="@{|/edition/${edition.id}/staff|}" th:text="#{edition.staff}"></a>
-    <a th:if="${@authorizationService.canViewEditionStatistics(edition.id)}" class="sidebar_item" th:href="@{|/edition/${edition.id}/statistics|}" th:text="#{general.statistics}"></a>
-</div>
-
-<div layout:fragment="content">
-
-    <div th:if="${edition.hidden and @authorizationService.canEditEdition(edition.id)}" class="in_content announcement">
-        <div class="announcement_accent" style="background-color: var(--secondary-orange);"></div>
-        <div class="announcement_text">
-            <p th:text="#{edition.hide.info}"></p>
-        </div>
-    </div>
-
-    <div th:replace="~{announcement/edition :: top}"></div>
-
-    <h1 class="title" th:text="|${edition.course.name} - ${edition.name}|"></h1>
-
-    <div class="info_row">
-        <div th:unless="${@authorizationService.isStudentInEdition(edition.id)}" class="card_stats card">
-            <div class="card_stats_row">
-                <span th:text="#{edition.total_members}"></span>
-                <span th:text="${roles.size()}"></span>
-            </div>
-            <div class="card_stats_row">
-                <span th:text="#{statistics.total_students}"></span>
-                <span th:text="${roles.?[type.name() == 'STUDENT'].size()}"></span>
-            </div>
-        </div>
-        <button th:if="${@authorizationService.canCreateModule(edition.id)}" onclick="toggleOverlay('add-module-overlay')" class="card interactive">
-            <span class="card_icon fas fa-plus-circle"></span>
-            <span th:text="#{module.add}"></span>
-        </button>
-
-        <div th:if="${@authorizationService.isStudentInEdition(edition.id)}" class="half card">
-            <div class="card_vertical-content">
-                <span th:text="${grade == null} ? #{grading.no_grade} : #{edition.grade(${grade})}"></span>
-            </div>
-        </div>
-        <div th:if="${@authorizationService.isStudentInEdition(edition.id)}" class="half card">
-            <div class="smaller card_vertical-content">
-                <span th:text="#{assignment.upcoming_deadline}"></span>
-                <div class="card_stats_row">
-                    <span th:text="${upcoming == null} ? #{assignment.no_upcoming_deadline} : ${#temporals.format(upcoming.deadline, 'dd-MM-yyyy HH:mm')}"></span>
-                    <span th:if="${upcoming != null}" th:text="${upcoming.name}"></span>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+    layout:decorate="~{container}">
+    <head>
+        <title th:text="|${edition.course.name} - ${edition.name}|"></title>
+    </head>
+
+    <body>
+        <main layout:fragment="content">
+            <th:block layout:replace="~{edition/top :: top}"></th:block>
+
+            <div class="flex vertical">
+                <div
+                    class="surface flex vertical gap-3"
+                    th:unless="${@authorizationService.isStudentInEdition(edition.id)}">
+                    <div class="underlined flex space-between pil-3">
+                        <span th:text="#{edition.total_members}"></span>
+                        <span th:text="${roles.size()}"></span>
+                    </div>
+                    <div class="underlined flex space-between pil-3">
+                        <span th:text="#{statistics.total_students}"></span>
+                        <span th:text="${roles.?[type.name() == 'STUDENT'].size()}"></span>
+                    </div>
                 </div>
-            </div>
-        </div>
-    </div>
 
-    <div class="edition_modules">
-        <div th:each="module : ${#lists.sort(edition.modules, @templateService.nameComparator)}">
-            <div th:id="|module-${module.id}|" th:href="@{|/module/${module.id}|}" class="collapsed edition_module interactive card"
-                 th:onclick="|toModule(${module.id})|">
-                <div class="edition_module_main">
-                    <span th:text="${module.name}"></span>
-                    <div class="edition_module_buttons">
-                        <span th:if="${@authorizationService.isStudentInEdition(edition.id)}" class="edition_module_group"
-                              th:text="${groups[module.id]?.name} ?: #{module.no_group}"></span>
-                        <button th:if="${!module.assignments.isEmpty()}" class="text-button" th:onclick="|toggleCollapse(${module.id})|"
-                                th:onmouseover="|toggleHoverInside(${module.id})|"
-                                th:onmouseout="|toggleHoverInside(${module.id})|"><span class="fas fa-chevron-down"></span></button>
+                <div>
+                    <div class="flex space-between align-center">
+                        <h3 class="font-500 mb-2">Modules</h3>
+                        <div class="flex gap-3">
+                            <button
+                                th:if="${@authorizationService.canCreateModule(edition.id)}"
+                                class="button"
+                                data-style="outlined"
+                                data-dialog="add-module-overlay"
+                                th:text="#{module.add}"></button>
+                        </div>
+                    </div>
+
+                    <div class="accordion">
+                        <th:block
+                            th:each="module : ${#lists.sort(edition.modules, @templateService.nameComparator)}">
+                            <div class="accordion__header pil-5 pbl-3 flex space-between">
+                                <a
+                                    class="link"
+                                    th:href="@{/module/{id}(id=${module.id})}"
+                                    th:text="${module.name}"></a>
+                                <button
+                                    class="fa-solid fa-chevron-down"
+                                    aria-expanded="false"
+                                    th:aria-controls="|module-${module.id}|"></button>
+                            </div>
+                            <ul
+                                th:id="|module-${module.id}|"
+                                class="accordion__content list flex vertical gap-3 mb-3"
+                                role="list"
+                                aria-expanded="false">
+                                <li th:if="${module.assignments.isEmpty()}" class="pil-5">
+                                    No assignments
+                                </li>
+                                <li
+                                    class="pil-5"
+                                    th:each="assignment : ${#lists.sort(module.assignments, @templateService.nameComparator)}">
+                                    <a
+                                        class="link"
+                                        th:href="@{/assignment/{id}(id=${assignment.id})}"
+                                        th:text="${assignment.name}"></a>
+                                </li>
+                            </ul>
+                        </th:block>
                     </div>
                 </div>
             </div>
-            <div class="edition_module_assignments">
-                <a th:each="assignment : ${#lists.sort(module.assignments, @templateService.nameComparator)}" th:href="@{|/assignment/${assignment.id}|}" class="edition_module_assignment">
-                    <span th:text="${assignment.name}"></span>
-                </a>
-            </div>
-        </div>
-    </div>
-
-    <div th:replace="~{edition/contact :: overlay}"></div>
-    <div th:replace="~{edition/archive :: overlay}"></div>
-    <div th:replace="~{edition/edit :: overlay}"></div>
-    <div th:replace="~{edition/export_grades :: overlay}"></div>
-    <div th:replace="~{edition/grading :: overlay}"></div>
-    <div th:replace="~{edition/hide :: overlay}"></div>
-    <div th:replace="~{module/add :: overlay}"></div>
-
-</div>
 
-</body>
-</html>
\ No newline at end of file
+            <div th:replace="~{edition/contact :: overlay}"></div>
+            <div th:replace="~{module/add :: overlay}"></div>
+        </main>
+    </body>
+</html>
diff --git a/src/main/resources/templates/error/bad_request.html b/src/main/resources/templates/error/bad_request.html
index a64d63755fb3555875d7c413874462ba7214870d..defcee23f9171d258744d4cd0b8449b3a2ead3f3 100644
--- a/src/main/resources/templates/error/bad_request.html
+++ b/src/main/resources/templates/error/bad_request.html
@@ -18,22 +18,18 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
-      layout:decorate="~{container}">
-<head>
-    <title th:text="#{general.app_name}"></title>
-</head>
-
-<body>
-
-<div layout:fragment="sidebar" class="hidden"></div>
-<div layout:fragment="breadcrumbs" class="hidden"></div>
-
-<div layout:fragment="content">
-
-    <h1 class="title">Bad request</h1>
-
-</div>
-
-</body>
-</html>
\ No newline at end of file
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+    layout:decorate="~{container}">
+    <head>
+        <title th:text="#{general.app_name}"></title>
+    </head>
+
+    <body>
+        <main layout:fragment="content">
+            <h1 class="font-800">Bad request</h1>
+        </main>
+    </body>
+</html>
diff --git a/src/main/resources/templates/error/error.html b/src/main/resources/templates/error/error.html
index 25039ab5720e40d929ca7265333c1677ae7b824b..ab7d92d801537b81ef3fd733fd7ace21621f2854 100644
--- a/src/main/resources/templates/error/error.html
+++ b/src/main/resources/templates/error/error.html
@@ -18,26 +18,23 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
-      layout:decorate="~{container}">
-<head>
-    <title th:text="#{general.app_name}"></title>
-</head>
-
-<body>
-
-<div layout:fragment="sidebar" class="hidden"></div>
-<div layout:fragment="breadcrumbs" class="hidden"></div>
-
-<div layout:fragment="content">
-
-    <h1 class="title">Oops! Something went wrong :(</h1>
-    <p>Do you want to help resolve this error?
-        <a href="https://gitlab.ewi.tudelft.nl/eip/labrador/submit" class="text-button">Submit</a> is open source.
-        You can file a bug report or contribute yourself!
-    </p>
-
-</div>
-
-</body>
-</html>
\ No newline at end of file
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+    layout:decorate="~{container}">
+    <head>
+        <title th:text="#{general.app_name}"></title>
+    </head>
+
+    <body>
+        <main layout:fragment="content">
+            <h1 class="font-800 mb-5">Oops! Something went wrong :(</h1>
+            <p>
+                Do you want to help resolve this error?
+                <a href="https://gitlab.ewi.tudelft.nl/eip/labrador/submit" class="link">Submit</a>
+                is open source. You can file a bug report or contribute yourself!
+            </p>
+        </main>
+    </body>
+</html>
diff --git a/src/main/resources/templates/error/forbidden.html b/src/main/resources/templates/error/forbidden.html
index e357526ffbd32289b959d3e6bd1276bcceeb080c..1b3604617c9682714c03cda0197a800d8ac2e3a4 100644
--- a/src/main/resources/templates/error/forbidden.html
+++ b/src/main/resources/templates/error/forbidden.html
@@ -18,22 +18,18 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
-      layout:decorate="~{container}">
-<head>
-    <title th:text="#{general.app_name}"></title>
-</head>
-
-<body>
-
-<div layout:fragment="sidebar" class="hidden"></div>
-<div layout:fragment="breadcrumbs" class="hidden"></div>
-
-<div layout:fragment="content">
-
-    <h1 class="title">You are not authorised to do that.</h1>
-
-</div>
-
-</body>
-</html>
\ No newline at end of file
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+    layout:decorate="~{container}">
+    <head>
+        <title th:text="#{general.app_name}"></title>
+    </head>
+
+    <body>
+        <main layout:fragment="content">
+            <h1 class="font-800">You are not authorised to do that.</h1>
+        </main>
+    </body>
+</html>
diff --git a/src/main/resources/templates/error/not_found.html b/src/main/resources/templates/error/not_found.html
index 51f0b8630547c9312153277234e787fbaa01508e..498eb4aea35b657408c4e49e2fda503f2b2d1548 100644
--- a/src/main/resources/templates/error/not_found.html
+++ b/src/main/resources/templates/error/not_found.html
@@ -18,22 +18,18 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
-      layout:decorate="~{container}">
-<head>
-    <title th:text="#{general.app_name}"></title>
-</head>
-
-<body>
-
-<div layout:fragment="sidebar" class="hidden"></div>
-<div layout:fragment="breadcrumbs" class="hidden"></div>
-
-<div layout:fragment="content">
-
-    <h1 class="title">What you are looking for is not (yet) there.</h1>
-
-</div>
-
-</body>
-</html>
\ No newline at end of file
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+    layout:decorate="~{container}">
+    <head>
+        <title th:text="#{general.app_name}"></title>
+    </head>
+
+    <body>
+        <main layout:fragment="content">
+            <h1 class="font-800">What you are looking for is not (yet) there.</h1>
+        </main>
+    </body>
+</html>
diff --git a/src/main/resources/templates/error/unprocessable_entity.html b/src/main/resources/templates/error/unprocessable_entity.html
index 8afb51c0446f9cb4f76bf28427669a9b88c59f50..51cd9e305f16d79d823a28c552afa5bb0db72acc 100644
--- a/src/main/resources/templates/error/unprocessable_entity.html
+++ b/src/main/resources/templates/error/unprocessable_entity.html
@@ -18,22 +18,18 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
-      layout:decorate="~{container}">
-<head>
-    <title th:text="#{general.app_name}"></title>
-</head>
-
-<body>
-
-<div layout:fragment="sidebar" class="hidden"></div>
-<div layout:fragment="breadcrumbs" class="hidden"></div>
-
-<div layout:fragment="content">
-
-    <h1 class="title">Entity was not valid</h1>
-
-</div>
-
-</body>
-</html>
\ No newline at end of file
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+    layout:decorate="~{container}">
+    <head>
+        <title th:text="#{general.app_name}"></title>
+    </head>
+
+    <body>
+        <div layout:fragment="content">
+            <h1 class="font-800">Entity was not valid</h1>
+        </div>
+    </body>
+</html>
diff --git a/src/main/resources/templates/footer.html b/src/main/resources/templates/footer.html
index 3df62b3b84a2e5e6a81880cdda1bedfc01136e42..aa1a7cec886eb644f859217c81d3999daa955a2a 100644
--- a/src/main/resources/templates/footer.html
+++ b/src/main/resources/templates/footer.html
@@ -18,8 +18,15 @@
 
 */-->
 
-<footer class="footer">
-    <div class="footer_content">
-        <a class="text-button" th:href="@{/privacy}" th:text="#{general.privacy_statement}"></a>
-    </div>
-</footer>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <footer class="footer flex space-between align-center" layout:fragment="footer">
+        <img th:src="@{/img/tudelft_logo_dark.png}" height="24px" alt="TU Delft" />
+
+        <div class="flex gap-7">
+            <a class="link" th:href="@{/privacy}" th:text="#{general.privacy_statement}"></a>
+        </div>
+    </footer>
+</html>
diff --git a/src/main/resources/templates/grading/help.html b/src/main/resources/templates/grading/help.html
index aebb7c0a740f335e5e4ca4639b0417f07a22a8be..205c169c1505a9768907aeecbff547ccf892e1f2 100644
--- a/src/main/resources/templates/grading/help.html
+++ b/src/main/resources/templates/grading/help.html
@@ -18,129 +18,207 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
-      layout:decorate="~{container}">
-<head>
-    <link rel="stylesheet" type="text/css" th:href="@{/css/article.css}"/>
-
-    <title th:text="#{general.app_name}"></title>
-</head>
-
-<body>
-
-<div layout:fragment="sidebar" class="hidden"></div>
-<div layout:fragment="breadcrumbs" class="hidden"></div>
-
-<div layout:fragment="content">
-    <h1 class="title" th:text="#{grading.formulas}"></h1>
-
-    <h2>Basic formulas</h2>
-    <p>
-        With grading formulas you can set up a custom calculation for module and edition grades.
-        When you write a formula, there are a few variables available.
-        For a module these will correspond to the grades of the assignments.
-        For an editions these will correspond to the grades of the modules.
-    </p>
-
-    <p>
-        You can apply several operations on those variables.
-        Available operations are:
-    </p>
-    <ul>
-        <li>Multiplication, e.g. <code>assignment_1 * 2</code></li>
-        <li>Division, e.g. <code>assignment_1 / 2</code></li>
-        <li>Addition, e.g. <code>assignment_1 + assignment_2</code></li>
-        <li>Subtraction, e.g. <code>assignment_1 - 1</code></li>
-        <li>Exponentiation, e.g. <code>assignment_1 ^ 1.1</code></li>
-        <li>Modulo, this is the remainder of a division. For example <code>5 mod 2 = 1</code>.
-            An example use case of this is rounding a number down or up, e.g.
-            <code>assignment_1 - assignment_1 mod 10</code>.
-            If the score for 'assignment 1' is 54 in this case, the result will be 50.</li>
-    </ul>
-
-    <h2>Conditions</h2>
-
-    <p>
-        Sometimes, you might want to give a different score based on a condition.
-        For example, the students might have to get higher than a 6 for assignment 1,
-        then assignment 2 will be their final score, otherwise they get a 1.0.
-        You can achieve this using <code>if</code>:
-        <code>assignment_2 if assignment_1 &gt; 6.0 else 1.0</code>
-    </p>
-
-    <p>
-        <code>&gt;</code> is one of the relational operators.
-        There are several of those as well:
-    </p>
-
-    <ul>
-        <li>Greater than, e.g. <code>assignment_1 &gt; 6.0</code></li>
-        <li>Less than, e.g. <code>assignment_1 &lt; 1.0</code></li>
-        <li>Greater than or equal, .e.g <code>assignment_1 &gt;= 6.0</code></li>
-        <li>Less than or equal, e.g. <code>assignment_1 &lt;= 1.0</code></li>
-        <li>Equal, e.g. <code>assignment_1 = 10.0</code></li>
-        <li>Not equal, e.g. <code>assignment_1 != 1.0</code></li>
-    </ul>
-
-    <p>
-        You might want to check for multiple conditions.
-        For example to check whether a score is between some values, let's say 1 and 5.
-        In that case you can use <code>and</code>, in which case both conditions need to be true.
-        So: <code>5.0 if assignment_1 >= 1 and assignment_1 <= 5 else assignment_1</code>.
-        The other thing you could do is check if either one condition is true,
-        i.e. the first condition, the second, or both.
-        Then you can use <code>or</code>.
-    </p>
-
-    <h2>Functions</h2>
-    <p>
-        There are some functions available for you to use.
-        These can be used to make some operations easier and more concise.
-        These functions are:
-    </p>
-
-    <ul>
-        <li><code>avg</code>: calculates the average of an arbitrary amount of numbers.
-            Usage: <code>avg(assignment_1, assignment_2, assignment_3)</code></li>
-        <li><code>min</code>: gets the lowest two numbers.
-            Usage: <code>min(assignment_1, 10.0)</code></li>
-        <li><code>max</code>: gets the highest of two numbers.
-            Usage: <code>max(assignment_1, 1.0)</code></li>
-        <li><code>clamp</code>: a combination of min and max, the middle number stays between the two outer numbers.
-            Usage: <code>clamp(1.0, assignment_1, 10.0)</code></li>
-        <li><code>sqrt</code>: calculates the square root of a number.
-            Usage: <code>sqrt(2.0)</code></li>
-        <li><code>abs</code>: calculates the absolute value of a number.
-            Usage: <code>abs(assignment_1)</code></li>
-    </ul>
-
-    <h2>Special values</h2>
-    <p>
-        You can use the special values <code>PASSED</code> and <code>FAILED</code> to check if the students passed an assignment.
-        These correspond to the values <code>1.0</code> and <code>0.0</code> respectively.
-        So writing <code>10.0 if assignment = PASSED else 1.0</code> is equivalent to <code>10.0 if assignment = 1.0 else 1.0</code>,
-        but might be slightly more clear.
-        You can also use these values to make the entire module or edition pass/fail,
-        in addition to setting the grade type to 'Pass / Fail'.
-    </p>
-
-    <p>
-        There are special versions of the assignment variables available as well:
-        one for checking if a score was created by a script and one for only human-made scores.
-        You can put <code>_isScript</code> or <code>_noScript</code> directly after the assignment name respectively.
-        So as an example: <code>assignment_1 / 2.0 if assignment_1_isScript else assignment_1</code> awards
-        the student half points if their score was script graded and full points if it was human graded.
-        <code>assignment_1_noScript</code> on the other hand, awards the student no points if their score was script graded.
-    </p>
-
-    <p>
-        Finally, two variables for calculating grades depending on when an assignment was submitted or graded exist.
-        Putting <code>_submissionTime</code> directly after the assignment name will give the submission time in hours relative to the deadline.
-        Putting <code>_gradeTime</code> directly after the assignment name will give the grading time in hours relative to the deadline.
-        This means that in most of the cases these numbers will be <b>negative</b>.
-    </p>
-
-</div>
-
-</body>
-</html>
\ No newline at end of file
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+    layout:decorate="~{container}">
+    <head>
+        <title th:text="#{general.app_name}"></title>
+
+        <style>
+            code {
+                background-color: var(--surface-colour);
+                color: var(--on-surface-colour);
+                white-space: nowrap;
+            }
+        </style>
+    </head>
+
+    <body>
+        <main layout:fragment="content" class="article">
+            <h1 th:text="#{grading.formulas}"></h1>
+
+            <h2>Basic formulas</h2>
+            <p>
+                With grading formulas you can set up a custom calculation for module and edition
+                grades. When you write a formula, there are a few variables available. For a module
+                these will correspond to the grades of the assignments. For an editions these will
+                correspond to the grades of the modules.
+            </p>
+
+            <p>You can apply several operations on those variables. Available operations are:</p>
+            <ul>
+                <li>
+                    Multiplication, e.g.
+                    <code>assignment_1 * 2</code>
+                </li>
+                <li>
+                    Division, e.g.
+                    <code>assignment_1 / 2</code>
+                </li>
+                <li>
+                    Addition, e.g.
+                    <code>assignment_1 + assignment_2</code>
+                </li>
+                <li>
+                    Subtraction, e.g.
+                    <code>assignment_1 - 1</code>
+                </li>
+                <li>
+                    Exponentiation, e.g.
+                    <code>assignment_1 ^ 1.1</code>
+                </li>
+                <li>
+                    Modulo, this is the remainder of a division. For example
+                    <code>5 mod 2 = 1</code>
+                    . An example use case of this is rounding a number down or up, e.g.
+                    <code>assignment_1 - assignment_1 mod 10</code>
+                    . If the score for 'assignment 1' is 54 in this case, the result will be 50.
+                </li>
+            </ul>
+
+            <h2>Conditions</h2>
+
+            <p>
+                Sometimes, you might want to give a different score based on a condition. For
+                example, the students might have to get higher than a 6 for assignment 1, then
+                assignment 2 will be their final score, otherwise they get a 1.0. You can achieve
+                this using
+                <code>if</code>
+                :
+                <code>assignment_2 if assignment_1 &gt; 6.0 else 1.0</code>
+            </p>
+
+            <p>
+                <code>&gt;</code>
+                is one of the relational operators. There are several of those as well:
+            </p>
+
+            <ul>
+                <li>
+                    Greater than, e.g.
+                    <code>assignment_1 &gt; 6.0</code>
+                </li>
+                <li>
+                    Less than, e.g.
+                    <code>assignment_1 &lt; 1.0</code>
+                </li>
+                <li>
+                    Greater than or equal, .e.g
+                    <code>assignment_1 &gt;= 6.0</code>
+                </li>
+                <li>
+                    Less than or equal, e.g.
+                    <code>assignment_1 &lt;= 1.0</code>
+                </li>
+                <li>
+                    Equal, e.g.
+                    <code>assignment_1 = 10.0</code>
+                </li>
+                <li>
+                    Not equal, e.g.
+                    <code>assignment_1 != 1.0</code>
+                </li>
+            </ul>
+
+            <p>
+                You might want to check for multiple conditions. For example to check whether a
+                score is between some values, let's say 1 and 5. In that case you can use
+                <code>and</code>
+                , in which case both conditions need to be true. So:
+                <code>5.0 if assignment_1 >= 1 and assignment_1 <= 5 else assignment_1</code>
+                . The other thing you could do is check if either one condition is true, i.e. the
+                first condition, the second, or both. Then you can use
+                <code>or</code>
+                .
+            </p>
+
+            <h2>Functions</h2>
+            <p>
+                There are some functions available for you to use. These can be used to make some
+                operations easier and more concise. These functions are:
+            </p>
+
+            <ul>
+                <li>
+                    <code>avg</code>
+                    : calculates the average of an arbitrary amount of numbers. Usage:
+                    <code>avg(assignment_1, assignment_2, assignment_3)</code>
+                </li>
+                <li>
+                    <code>min</code>
+                    : gets the lowest two numbers. Usage:
+                    <code>min(assignment_1, 10.0)</code>
+                </li>
+                <li>
+                    <code>max</code>
+                    : gets the highest of two numbers. Usage:
+                    <code>max(assignment_1, 1.0)</code>
+                </li>
+                <li>
+                    <code>clamp</code>
+                    : a combination of min and max, the middle number stays between the two outer
+                    numbers. Usage:
+                    <code>clamp(1.0, assignment_1, 10.0)</code>
+                </li>
+                <li>
+                    <code>sqrt</code>
+                    : calculates the square root of a number. Usage:
+                    <code>sqrt(2.0)</code>
+                </li>
+                <li>
+                    <code>abs</code>
+                    : calculates the absolute value of a number. Usage:
+                    <code>abs(assignment_1)</code>
+                </li>
+            </ul>
+
+            <h2>Special values</h2>
+            <p>
+                You can use the special values
+                <code>PASSED</code>
+                and
+                <code>FAILED</code>
+                to check if the students passed an assignment. These correspond to the values
+                <code>1.0</code>
+                and
+                <code>0.0</code>
+                respectively. So writing
+                <code>10.0 if assignment = PASSED else 1.0</code>
+                is equivalent to
+                <code>10.0 if assignment = 1.0 else 1.0</code>
+                , but might be slightly more clear. You can also use these values to make the entire
+                module or edition pass/fail, in addition to setting the grade type to 'Pass / Fail'.
+            </p>
+
+            <p>
+                There are special versions of the assignment variables available as well: one for
+                checking if a score was created by a script and one for only human-made scores. You
+                can put
+                <code>_isScript</code>
+                or
+                <code>_noScript</code>
+                directly after the assignment name respectively. So as an example:
+                <code>assignment_1 / 2.0 if assignment_1_isScript else assignment_1</code>
+                awards the student half points if their score was script graded and full points if
+                it was human graded.
+                <code>assignment_1_noScript</code>
+                on the other hand, awards the student no points if their score was script graded.
+            </p>
+
+            <p>
+                Finally, two variables for calculating grades depending on when an assignment was
+                submitted or graded exist. Putting
+                <code>_submissionTime</code>
+                directly after the assignment name will give the submission time in hours relative
+                to the deadline. Putting
+                <code>_gradeTime</code>
+                directly after the assignment name will give the grading time in hours relative to
+                the deadline. This means that in most of the cases these numbers will be
+                <b>negative</b>
+                .
+            </p>
+        </main>
+    </body>
+</html>
diff --git a/src/main/resources/templates/group/add_member.html b/src/main/resources/templates/group/add_member.html
index 5528ee72d9efe6107ca25d870179ddab1576ca93..c86f558139b59853188cb8ac582633295c90361c 100644
--- a/src/main/resources/templates/group/add_member.html
+++ b/src/main/resources/templates/group/add_member.html
@@ -18,23 +18,41 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
-
-<div th:fragment="overlay" th:if="${@authorizationService.canAddMembersToGroup(group.id)}" id="add-member-overlay" class="hidden overlay">
-    <form th:action="@{|/group/${group.id}/members|}" method="post" class="boxed-content">
-        <h1 class="underlined title" th:text="#{group.add_member}"></h1>
-        <div class="form-group">
-            <label for="username" th:text="#{person.username}"></label>
-            <input id="username" name="usernames" th:placeholder="#{person.username.enter}" type="text" class="textfield" required/>
-        </div>
-        <div class="form-buttons">
-            <button type=button class="warning text-button" th:text="#{general.cancel}"
-                    onclick="toggleOverlay('add-member-overlay')"></button>
-            <button type=submit class="text-button" th:text="#{general.save}"></button>
-        </div>
-    </form>
-</div>
-
-</body>
-</html>
\ No newline at end of file
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <dialog
+            th:fragment="overlay"
+            th:if="${@authorizationService.canAddMembersToGroup(group.id)}"
+            id="add-member-overlay"
+            class="dialog">
+            <form
+                th:action="@{|/group/${group.id}/members|}"
+                method="post"
+                class="flex vertical p-7">
+                <h1 class="underlined font-500" th:text="#{group.add_member}"></h1>
+                <div class="grid col-2 align-center gap-3" style="--col-1: minmax(0, 8rem)">
+                    <label for="username" th:text="#{person.username}"></label>
+                    <input
+                        id="username"
+                        name="usernames"
+                        th:placeholder="#{person.username.enter}"
+                        type="text"
+                        class="textfield"
+                        required />
+                </div>
+                <div class="flex space-between">
+                    <button
+                        type="button"
+                        class="button p-less"
+                        data-style="outlined"
+                        th:text="#{general.cancel}"
+                        data-cancel></button>
+                    <button type="submit" class="button p-less" th:text="#{general.save}"></button>
+                </div>
+            </form>
+        </dialog>
+    </body>
+</html>
diff --git a/src/main/resources/templates/group/download_submissions.html b/src/main/resources/templates/group/download_submissions.html
index 0a25b125fa267fc9fd900dfb496fa4f6819436cd..704a3811a1b99c8fcdacb032ab60275e0b75181b 100644
--- a/src/main/resources/templates/group/download_submissions.html
+++ b/src/main/resources/templates/group/download_submissions.html
@@ -18,35 +18,62 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
-
-<div th:fragment="overlay" th:if="${@authorizationService.canDownloadGroupSubmissions(group.id)}" id="download-overlay" class="hidden overlay">
-    <form th:object="${submissionDownloadConfigDto}" th:action="@{|/submission/group/${group.id}/download|}" method="get" class="boxed-content">
-        <h1 class="underlined title" th:text="#{group.download_submissions}"></h1>
-        <div class="form-group">
-            <span th:text="#{group.download_submissions.choose_assignments}"></span>
-            <div class="form-vstack">
-                <div th:each="assignment : ${module.assignments}" class="checkbox">
-                    <input th:id="|assignment-${assignment.id}-box|" th:name="assignmentChoice" th:value="${assignment.id}" type="checkbox"/>
-                    <label th:for="|assignment-${assignment.id}-box|" th:text="${assignment.name}"></label>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <dialog
+            th:fragment="overlay"
+            th:if="${@authorizationService.canDownloadGroupSubmissions(group.id)}"
+            id="download-overlay"
+            class="dialog">
+            <form
+                th:object="${submissionDownloadConfigDto}"
+                th:action="@{|/submission/group/${group.id}/download|}"
+                method="get"
+                class="flex vertical p-7">
+                <h2 class="underlined font-500" th:text="#{group.download_submissions}"></h2>
+
+                <div class="grid col-2" style="--col-1: minmax(0, 10rem)">
+                    <span th:text="#{group.download_submissions.choose_assignments}"></span>
+                    <div class="flex vertical gap-1">
+                        <div th:each="assignment : ${module.assignments}" class="checkbox">
+                            <input
+                                th:id="|assignment-${assignment.id}-box|"
+                                th:name="assignmentChoice"
+                                th:value="${assignment.id}"
+                                type="checkbox" />
+                            <label
+                                th:for="|assignment-${assignment.id}-box|"
+                                th:text="${assignment.name}"></label>
+                        </div>
+                    </div>
+
+                    <label
+                        for="submissions-choice"
+                        th:text="#{group.download_submissions.choose_submissions}"></label>
+                    <select id="submissions-choice" th:name="submissionChoice" class="textfield">
+                        <option
+                            th:each="choice : ${T(nl.tudelft.submit.enums.SubmissionDownloadPreference).values()}"
+                            th:value="${choice}"
+                            th:text="#{|group.download_submissions.choice.${#strings.toLowerCase(choice.name())}|}"></option>
+                    </select>
+                </div>
+
+                <div class="flex space-between">
+                    <button
+                        type="button"
+                        class="button p-less"
+                        data-style="outlined"
+                        th:text="#{general.cancel}"
+                        data-cancel></button>
+                    <button
+                        type="submit"
+                        class="button p-less"
+                        th:text="#{general.download}"></button>
                 </div>
-            </div>
-
-            <label for="submissions-choice" th:text="#{group.download_submissions.choose_submissions}"></label>
-            <select id="submissions-choice" th:name="submissionChoice" class="selectbox">
-                <option th:each="choice : ${T(nl.tudelft.submit.enums.SubmissionDownloadPreference).values()}"
-                        th:value="${choice}"
-                        th:text="#{|group.download_submissions.choice.${#strings.toLowerCase(choice.name())}|}"></option>
-            </select>
-        </div>
-        <div class="form-buttons">
-            <button type=button class="warning text-button" th:text="#{general.cancel}"
-                    onclick="toggleOverlay('download-overlay')"></button>
-            <button type=submit class="text-button" th:text="#{general.download}"></button>
-        </div>
-    </form>
-</div>
-
-</body>
-</html>
\ No newline at end of file
+            </form>
+        </dialog>
+    </body>
+</html>
diff --git a/src/main/resources/templates/group/leave.html b/src/main/resources/templates/group/leave.html
index 73b862484d7bd86018f71b49b4048c2e336009cc..f02184c2757245070976a7cfe1b05512435e40dc 100644
--- a/src/main/resources/templates/group/leave.html
+++ b/src/main/resources/templates/group/leave.html
@@ -18,19 +18,27 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
-
-<div th:fragment="overlay" th:if="${@authorizationService.canLeaveGroup(group.id)}" id="leave-overlay" class="hidden confirm overlay">
-    <form th:action="@{|/group/${group.id}/leave|}" method="post" class="boxed-content">
-        <p class="confirm_text" th:text="#{group.leave.confirm}"></p>
-        <div class="form-buttons">
-            <button type=button class="warning text-button" th:text="#{general.cancel}"
-                    onclick="toggleOverlay('leave-overlay')"></button>
-            <button type=submit class="text-button" th:text="#{group.leave}"></button>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <div
+            th:fragment="overlay"
+            th:if="${@authorizationService.canLeaveGroup(group.id)}"
+            id="leave-overlay"
+            class="hidden confirm overlay">
+            <form th:action="@{|/group/${group.id}/leave|}" method="post" class="boxed-content">
+                <p class="confirm_text" th:text="#{group.leave.confirm}"></p>
+                <div class="form-buttons">
+                    <button
+                        type="button"
+                        class="warning text-button"
+                        th:text="#{general.cancel}"
+                        onclick="toggleOverlay('leave-overlay')"></button>
+                    <button type="submit" class="text-button" th:text="#{group.leave}"></button>
+                </div>
+            </form>
         </div>
-    </form>
-</div>
-
-</body>
-</html>
\ No newline at end of file
+    </body>
+</html>
diff --git a/src/main/resources/templates/group/note.html b/src/main/resources/templates/group/note.html
index 4cf87f7c82f898b0a2528eea16912273a96f6767..6f893c7560e4f883cac57abb4e1b971d6a0442d0 100644
--- a/src/main/resources/templates/group/note.html
+++ b/src/main/resources/templates/group/note.html
@@ -18,27 +18,49 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <dialog
+            th:fragment="overlay"
+            th:if="${@authorizationService.canEditGroupNote(group.id)}"
+            id="note-overlay"
+            class="dialog">
+            <form
+                th:object="${note}"
+                th:action="@{/group/note}"
+                method="post"
+                class="flex vertical p-7">
+                <h2 class="underlined font-500" th:text="#{group.note}"></h2>
 
-<div th:fragment="overlay" th:if="${@authorizationService.canEditGroupNote(group.id)}" id="note-overlay" class="hidden overlay">
-    <form th:object="${note}" th:action="@{/group/note}" method="post" class="boxed-content">
-        <h1 class="underlined title" th:text="#{group.note}"></h1>
-        <div class="form-group">
-            <input type="hidden" th:name="entityId.groupId" th:value="${group.id}"/>
-            <span th:text="#{general.for}"></span>
-            <span th:text="${group.name}"></span>
-            <label for="note" th:text="#{general.note}"></label>
-            <textarea id="note" th:name="note" th:placeholder="#{general.note.enter}" th:text="${group.note?.note}"
-                      rows="6" type="text" class="textarea" required></textarea>
-        </div>
-        <div class="form-buttons">
-            <button type=button class="warning text-button" th:text="#{general.cancel}"
-                    onclick="toggleOverlay('note-overlay')"></button>
-            <button type=submit class="text-button" th:text="#{general.save}"></button>
-        </div>
-    </form>
-</div>
+                <div class="grid col-2" style="--col-1: minmax(0, 8rem)">
+                    <input type="hidden" th:name="entityId.groupId" th:value="${group.id}" />
+                    <span th:text="#{general.for}"></span>
+                    <span th:text="${group.name}"></span>
+                    <label for="note" th:text="#{general.note}"></label>
+                    <textarea
+                        id="note"
+                        th:name="note"
+                        th:placeholder="#{general.note.enter}"
+                        th:text="${group.note?.note}"
+                        rows="6"
+                        type="text"
+                        class="textfield"
+                        required></textarea>
+                </div>
 
-</body>
-</html>
\ No newline at end of file
+                <div class="flex space-between">
+                    <button
+                        type="button"
+                        class="button p-less"
+                        data-style="outlined"
+                        th:text="#{general.cancel}"
+                        data-cancel></button>
+                    <button type="submit" class="button p-less" th:text="#{general.save}"></button>
+                </div>
+            </form>
+        </dialog>
+    </body>
+</html>
diff --git a/src/main/resources/templates/group/remove_member.html b/src/main/resources/templates/group/remove_member.html
index aec87aca00101a9bf3ab1c2d892c2b38d1000a98..55f4896341c1c32e7f1f77efc2bc4a8d6eadcb6c 100644
--- a/src/main/resources/templates/group/remove_member.html
+++ b/src/main/resources/templates/group/remove_member.html
@@ -18,19 +18,36 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
-
-<div th:fragment="overlay" th:if="${@authorizationService.canRemoveMemberFromGroup(group.id, member)}" th:id="|remove-${iter.index}-overlay|" class="hidden confirm overlay">
-    <form th:action="@{|/group/${group.id}/remove/username/${member}|}" th:method="patch" class="boxed-content">
-        <p class="confirm_text" th:text="#{group.remove_member.confirm(${member})}"></p>
-        <div class="form-buttons">
-            <button type=button class="warning text-button" th:text="#{general.cancel}"
-                    th:onclick="|toggleOverlay('remove-${iter.index}-overlay')|"></button>
-            <button type=submit class="text-button" th:text="#{general.remove}"></button>
-        </div>
-    </form>
-</div>
-
-</body>
-</html>
\ No newline at end of file
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <dialog
+            th:fragment="overlay"
+            th:if="${@authorizationService.canRemoveMemberFromGroup(group.id, member)}"
+            th:id="|remove-${iter.index}-overlay|"
+            class="dialog">
+            <form
+                th:action="@{|/group/${group.id}/remove/username/${member}|}"
+                th:method="patch"
+                class="flex vertical p-7">
+                <h2 class="font-500 underlined">Remove member</h2>
+                <p th:text="#{group.remove_member.confirm(${member})}"></p>
+                <div class="flex space-between">
+                    <button
+                        type="button"
+                        class="button p-less"
+                        data-style="outlined"
+                        th:text="#{general.cancel}"
+                        data-cancel></button>
+                    <button
+                        type="submit"
+                        class="button p-less"
+                        data-type="error"
+                        th:text="#{general.remove}"></button>
+                </div>
+            </form>
+        </dialog>
+    </body>
+</html>
diff --git a/src/main/resources/templates/group/view.html b/src/main/resources/templates/group/view.html
index eac36e90d42709e08e2d392f120f5530fb67bba9..50ea759293e71bdf06d1fe93e829cc7b56881f2e 100644
--- a/src/main/resources/templates/group/view.html
+++ b/src/main/resources/templates/group/view.html
@@ -18,201 +18,292 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
-      layout:decorate="~{container}">
-<head>
-    <title th:text="${group.name}"></title>
-</head>
-
-<body>
-
-<div layout:fragment="breadcrumbs">
-    <a th:href="@{/edition/enrolled}" th:text="#{edition.editions}"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{|/edition/${module.edition.id}|}" th:text="|${edition.course.name} - ${edition.name}|"></a>
-    <div th:if="${@authorizationService.canViewModule(module.id)}" th:remove="tag">
-        <span>&nbsp;&gt;&nbsp;</span>
-        <a th:href="@{|/module/${module.id}|}" th:text="${module.name}"></a>
-        <span>&nbsp;&gt;&nbsp;</span>
-        <a th:href="@{|/module/${module.id}/groups|}" th:text="#{module.groups}"></a>
-    </div>
-    <div th:unless="${@authorizationService.canViewModule(module.id)}" th:remove="tag">
-        <span>&nbsp;&gt;&nbsp;</span>
-        <a th:href="@{|/module/${module.id}/groups|}" th:text="${module.name}"></a>
-    </div>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{|/group/${group.id}|}" th:text="${group.name}"></a>
-</div>
-
-<div layout:fragment="sidebar">
-    <button th:if="${@authorizationService.canDownloadGroupSubmissions(group.id)}"
-            class="sidebar_item" onclick="toggleOverlay('download-overlay')" th:text="#{group.download_submissions}"></button>
-    <button th:if="${@authorizationService.canLeaveGroup(group.id)}"
-            class="warning sidebar_item" onclick="toggleOverlay('leave-overlay')" th:text="#{group.leave}"></button>
-</div>
-
-<div layout:fragment="content">
-
-    <div th:with="edition = ${module.edition}" th:remove="tag">
-        <div th:replace="~{announcement/edition :: top}"></div>
-    </div>
-
-    <h1 class="title" th:text="|${module.name} - ${group.name}|"></h1>
-
-    <div th:if="${@authorizationService.isStudentInModule(module.id)}" class="info_row">
-        <div class="half card">
-            <div class="card_vertical-content">
-                <span th:text="${group.name}"></span>
-                <span th:text="${finalGrade} ?: #{group.no_grade}"></span>
-            </div>
-        </div>
-
-        <div class="half card">
-            <div class="smaller card_vertical-content">
-                <span th:text="#{assignment.upcoming_deadline}"></span>
-                <div class="card_stats_row">
-                    <span th:text="${upcoming == null} ? #{assignment.no_upcoming_deadline} : ${#temporals.format(upcoming.deadline, 'dd-MM-yyyy HH:mm')}"></span>
-                    <span th:if="${upcoming != null}" th:text="${upcoming.name}"></span>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+    layout:decorate="~{container}">
+    <head>
+        <title th:text="${group.name}"></title>
+    </head>
+
+    <body>
+        <main layout:fragment="content">
+            <div class="breadcrumbs mb-5">
+                <a th:href="@{/edition/enrolled}" th:text="#{edition.editions}"></a>
+                <span>&gt;</span>
+                <a
+                    th:href="@{|/edition/${module.edition.id}|}"
+                    th:text="|${edition.course.name} - ${edition.name}|"></a>
+                <div th:if="${@authorizationService.canViewModule(module.id)}" th:remove="tag">
+                    <span>&gt;</span>
+                    <a th:href="@{|/module/${module.id}|}" th:text="${module.name}"></a>
+                    <span>&gt;</span>
+                    <a th:href="@{|/module/${module.id}/groups|}" th:text="#{module.groups}"></a>
                 </div>
-            </div>
-        </div>
-    </div>
-
-    <div class="tabs">
-        <button id="submissions-tab" class="active tab" th:text="#{assignment.submissions}" onclick="switchTab('submissions')"></button>
-        <button id="members-tab" class="tab" th:text="#{group.members}" onclick="switchTab('members')"></button>
-    </div>
-
-    <div id="submissions">
-        <div th:unless="${@authorizationService.isStudentInModule(module.id)}" class="info_row">
-            <div class="half card">
-                <div class="card_vertical-content">
-                    <span th:text="${group.name}"></span>
-                    <span th:text="${finalGrade} ?: #{group.no_grade}"></span>
+                <div th:unless="${@authorizationService.canViewModule(module.id)}" th:remove="tag">
+                    <span>&gt;</span>
+                    <a th:href="@{|/module/${module.id}/groups|}" th:text="${module.name}"></a>
                 </div>
+                <span>&gt;</span>
+                <a th:href="@{|/group/${group.id}|}" th:text="${group.name}"></a>
             </div>
 
-            <div th:if="${@authorizationService.canEditGroupNote(group.id)}" class="half card">
-                <div class="card_content-with-icon">
-                    <p th:utext="${group.note == null} ? #{group.no_note} : ${#strings.replace(#strings.escapeXml(group.note.note), '&#10;', '<br>')}"></p>
-                    <button onclick="toggleOverlay('note-overlay')" class="icon-button"><i class="far fa-edit"></i></button>
-                </div>
+            <div th:with="edition = ${module.edition}" th:remove="tag">
+                <div layout:replace="~{announcement/edition :: top}"></div>
             </div>
-        </div>
-
-        <h2 th:unless="${@authorizationService.isStudentInModule(module.id)}" th:text="#{assignment.submissions}"></h2>
-        <div id="assignments" class="list">
-            <div th:each="entry : ${group.submissionMap.entrySet()}" th:with="assignment = ${entry.key}, submissions = ${entry.value}, latest = ${submissions.isEmpty() ? null : submissions[0]}, grade = ${latest?.getMaxGrade()}">
-                <div th:data-item="${assignment.id}" class="collapsed list_item">
-                    <div class="list_item_content">
-                        <span th:text="${assignment.name}"></span>
-                        <span class="tag" style="float: none;" th:if="${grade != null}" th:text="${@gradeService.getScoreDisplayString(grade)}"></span>
-                        <span class="tag" style="float: none;" th:if="${latest != null and latest.scriptPending}" th:text="#{submission.script_pending}"></span>
-                        <th:block th:unless="${latest?.scriptResults?.isEmpty()} ?: true" th:with="trainResult = ${latest.scriptResults[0]}">
-                            <span th:if="${trainResult.status.name() == 'FAILED'}" class="tag" style="float: none;" th:text="#{script.failed}"></span>
-                        </th:block>
-                    </div>
-                    <div class="list_actions">
-                        <a th:if="${@authorizationService.canViewAssignment(assignment.id)}" class="text-button" th:href="@{|/assignment/${assignment.id}|}" th:text="#{general.details}"></a>
-                        <a th:if="${submissions.size() > 0} and ${@authorizationService.canDownloadSubmission(submissions[0].id)}"
-                           th:href="@{|/submission/${submissions[0].id}/download|}" class="text-button" th:text="#{group.latest_submission.download}"></a>
-                        <a th:if="${submissions.size() > 0} and ${@authorizationService.canGradeSubmission(submissions[0].id)}" class="text-button"
-                           th:onclick="|toggleOverlay('feedback-${submissions[0].id}-overlay')|" th:text="#{grading.grade}"></a>
-                        <button th:if="${@authorizationService.canSubmitAssignment(assignment.id, group.id)}"
-                                th:disabled="${not canMakeSubmission[assignment.id]}"
-                                th:onclick="|toggleOverlay('submit-${assignment.id}-overlay')|" class="text-button" th:text="#{assignment.submit}"></button>
-                        <button class="text-button" th:onclick="|toggleCollapse('assignments', '${assignment.id}')|"><i class="fas fa-chevron-down"></i></button>
+
+            <h1 class="font-800 mb-3" th:text="|${module.name} - ${group.name}|"></h1>
+
+            <div class="flex gap-3 mb-5">
+                <button
+                    th:if="${@authorizationService.canDownloadGroupSubmissions(group.id)}"
+                    class="button"
+                    data-style="outlined"
+                    data-dialog="download-overlay"
+                    th:text="#{group.download_submissions}"></button>
+                <button
+                    th:if="${@authorizationService.canAddMembersToGroup(group.id)}"
+                    class="button"
+                    data-style="outlined"
+                    data-dialog="add-member-overlay"
+                    th:text="#{group.add_member}"></button>
+                <button
+                    th:if="${@authorizationService.canLeaveGroup(group.id)}"
+                    class="button"
+                    data-style="outlined"
+                    data-type="error"
+                    data-dialog="leave-overlay"
+                    th:text="#{group.leave}"></button>
+            </div>
+
+            <div
+                th:if="${@authorizationService.isStudentInModule(module.id)}"
+                class="grid col-2 mb-5">
+                <div class="surface">
+                    <div class="flex vertical gap-3">
+                        <span class="font-400">Grade</span>
+                        <span th:text="${finalGrade} ?: #{group.no_grade}"></span>
                     </div>
                 </div>
-                <div class="list_subitems">
-                    <span class="list_subitem" th:if="${submissions.size() == 0}" th:text="#{assignment.no_submissions}"></span>
-                    <div th:each="submission, iter : ${submissions}" class="list_subitem">
-                        <div class="list_subitem_content">
-                            <span th:text="#{submission.name(${submissions.size() - iter.index})}"></span>
-                            <span th:text="|(${#temporals.format(submission.submissionTime, 'dd-MM-yyyy HH:mm')})|"></span>
-                            <span class="tag" style="float: none;" th:if="${submission.late}" th:text="#{submission.late}"></span>
-                            <span class="tag" style="float: none;" th:if="${submission.scriptPending}" th:text="#{submission.script_pending}"></span>
-                            <th:block th:unless="${submission.scriptResults.isEmpty()} ?: true" th:with="trainResult = ${submission.scriptResults[0]}">
-                                <span th:if="${trainResult.status.name() == 'FAILED'}" class="tag" style="float: none;" th:text="#{script.failed}"></span>
-                            </th:block>
-                        </div>
-                        <div class="list_actions">
-                            <button th:if="${@authorizationService.canEditSubmissionNote(submission.id)}" class="text-button"
-                                    th:onclick="|toggleOverlay('note-${submission.id}-overlay')|" th:text="#{general.note}"></button>
-                            <a th:href="@{|/submission/${submission.id}/download|}" class=text-button th:text="#{general.download}"></a>
-                            <button th:if="${@authorizationService.canResubmitSubmission(submission.id)}" class="text-button"
-                                    th:onclick="|jQuery.post('/submission/${submission.id}/resubmit'); this.disabled = true;|" th:text="#{submission.resubmit}"></button>
-                            <button class="text-button" th:onclick="|toggleOverlay('feedback-${submission.id}-overlay')|" th:text="#{submission.feedback}"></button>
+
+                <div class="surface">
+                    <div class="flex vertical gap-3">
+                        <span class="font-400" th:text="#{assignment.upcoming_deadline}"></span>
+                        <div class="flex space-between underlined">
+                            <span th:if="${upcoming != null}" th:text="${upcoming.name}"></span>
+                            <span
+                                th:text="${upcoming == null} ? #{assignment.no_upcoming_deadline} : ${#temporals.format(upcoming.deadline, 'dd-MM-yyyy HH:mm')}"></span>
                         </div>
                     </div>
                 </div>
             </div>
-        </div>
-    </div>
-
-    <div id="members" class="hidden">
-        <div th:unless="${@authorizationService.isStudentInModule(module.id)}" class="info_row">
-            <div class="half card">
-                <div class="card_vertical-content">
-                    <span th:text="${group.name}"></span>
-                    <span th:text="${finalGrade} ?: #{group.no_grade}"></span>
+
+            <div
+                th:unless="${@authorizationService.isStudentInModule(module.id)}"
+                class="grid col-2 mb-5 | sm:col-1">
+                <div class="surface">
+                    <div class="flex vertical gap-3">
+                        <span class="font-400">Grade</span>
+                        <span th:text="${finalGrade} ?: #{group.no_grade}"></span>
+                    </div>
+                </div>
+
+                <div th:if="${@authorizationService.canEditGroupNote(group.id)}" class="surface">
+                    <div class="flex vertical align-start gap-3">
+                        <span class="font-400">Note</span>
+                        <p
+                            th:utext="${group.note == null} ? #{group.no_note} : ${#strings.replace(#strings.escapeXml(group.note.note), '&#10;', '<br>')}"></p>
+                        <button
+                            data-dialog="note-overlay"
+                            class="button p-less"
+                            data-style="outlined">
+                            Edit
+                        </button>
+                    </div>
                 </div>
             </div>
 
-            <button th:if="${@authorizationService.canAddMembersToGroup(group.id)}" onclick="toggleOverlay('add-member-overlay')" class="half card interactive">
-                <span class="card_icon fas fa-plus-circle"></span>
-                <span th:text="#{group.add_member}"></span>
-            </button>
-        </div>
-
-        <h2 th:unless="${@authorizationService.isStudentInModule(module.id)}" th:text="#{group.members}"></h2>
-        <div class="list">
-            <div th:each="member, iter : ${group.memberUsernames}" class="list_item">
-                <span class="list_item_content" th:text="${member}"></span>
-                <div class="list_actions">
-                    <a th:if="${@authorizationService.canViewPerson(member)}" class="text-button" th:href="@{|/person/username${'?'}username=${member}|}" th:text="#{general.details}"></a>
-                    <button th:if="${not @authorizationService.isStudentInModule(module.id) && @authorizationService.canRemoveMemberFromGroup(group.id, member)}" class="warning icon-button" type=button th:onclick="|toggleOverlay('remove-${iter.index}-overlay')|"><span class="far fa-trash-alt"></span></button>
+            <div class="tabs mb-5" role="tablist">
+                <button
+                    id="submissions-tab"
+                    role="tab"
+                    aria-controls="submissions"
+                    aria-selected="true"
+                    th:text="#{assignment.submissions}"></button>
+                <button
+                    id="members-tab"
+                    role="tab"
+                    aria-controls="members"
+                    aria-selected="false"
+                    th:text="#{group.members}"></button>
+            </div>
+
+            <div id="submissions" aria-labelledby="submissions-tab">
+                <ul id="assignments" class="accordion divided list" role="list">
+                    <li
+                        class="pbl-3 pil-5"
+                        th:each="entry : ${group.submissionMap.entrySet()}"
+                        th:with="assignment = ${entry.key}, submissions = ${entry.value}, latest = ${submissions.isEmpty() ? null : submissions[0]}, grade = ${latest?.getMaxGrade()}">
+                        <div th:data-item="${assignment.id}" class="accordion__header">
+                            <div class="flex space-between">
+                                <div>
+                                    <a
+                                        th:if="${@authorizationService.canViewAssignment(assignment.id)}"
+                                        class="link"
+                                        th:href="@{|/assignment/${assignment.id}|}"
+                                        th:text="${assignment.name}"></a>
+
+                                    <span
+                                        th:unless="${@authorizationService.canViewAssignment(assignment.id)}"
+                                        th:text="${assignment.name}"></span>
+                                    <span
+                                        class="chip"
+                                        th:if="${grade != null}"
+                                        th:text="${@gradeService.getScoreDisplayString(grade)}"></span>
+                                    <span
+                                        class="chip"
+                                        th:if="${latest != null and latest.scriptPending}"
+                                        th:text="#{submission.script_pending}"></span>
+                                    <th:block
+                                        th:unless="${latest?.scriptResults?.isEmpty()} ?: true"
+                                        th:with="trainResult = ${latest.scriptResults[0]}">
+                                        <span
+                                            th:if="${trainResult.status.name() == 'FAILED'}"
+                                            class="chip"
+                                            th:text="#{script.failed}"></span>
+                                    </th:block>
+                                </div>
+
+                                <div class="flex gap-3 font-300">
+                                    <a
+                                        th:if="${submissions.size() > 0} and ${@authorizationService.canDownloadSubmission(submissions[0].id)}"
+                                        th:href="@{|/submission/${submissions[0].id}/download|}"
+                                        class="button p-min"
+                                        data-style="outlined"
+                                        th:text="#{group.latest_submission.download}"></a>
+                                    <a
+                                        th:if="${submissions.size() > 0} and ${@authorizationService.canGradeSubmission(submissions[0].id)}"
+                                        class="button p-min"
+                                        data-style="outlined"
+                                        th:data-dialog="|feedback-${submissions[0].id}-overlay|"
+                                        th:text="#{grading.grade}"></a>
+                                    <button
+                                        th:if="${@authorizationService.canSubmitAssignment(assignment.id, group.id)}"
+                                        th:disabled="${not canMakeSubmission[assignment.id]}"
+                                        th:data-dialog="|submit-${assignment.id}-overlay|"
+                                        class="button p-min"
+                                        th:data-style="${submissions.isEmpty()} ? 'filled' : 'outlined'"
+                                        th:text="#{assignment.submit}"></button>
+                                    <button
+                                        class="fa-solid fa-chevron-down"
+                                        th:aria-controls="|assignment-${assignment.id}|"></button>
+                                </div>
+                            </div>
+                        </div>
+                        <div class="accordion__content" th:id="|assignment-${assignment.id}|">
+                            <span
+                                th:if="${submissions.size() == 0}"
+                                th:text="#{assignment.no_submissions}"></span>
+                            <div
+                                th:each="submission, iter : ${submissions}"
+                                class="flex space-between mt-2">
+                                <div>
+                                    <span
+                                        th:text="#{submission.name(${submissions.size() - iter.index})}"></span>
+                                    <span
+                                        th:text="|(${#temporals.format(submission.submissionTime, 'dd-MM-yyyy HH:mm')})|"></span>
+                                    <span
+                                        class="chip"
+                                        th:if="${submission.late}"
+                                        th:text="#{submission.late}"></span>
+                                    <span
+                                        class="chip"
+                                        th:if="${submission.scriptPending}"
+                                        th:text="#{submission.script_pending}"></span>
+                                    <th:block
+                                        th:unless="${submission.scriptResults.isEmpty()} ?: true"
+                                        th:with="trainResult = ${submission.scriptResults[0]}">
+                                        <span
+                                            th:if="${trainResult.status.name() == 'FAILED'}"
+                                            class="chip"
+                                            th:text="#{script.failed}"></span>
+                                    </th:block>
+                                </div>
+                                <div class="flex gap-3">
+                                    <button
+                                        th:if="${@authorizationService.canEditSubmissionNote(submission.id)}"
+                                        class="button p-min"
+                                        data-style="outlined"
+                                        th:data-dialog="|note-${submission.id}-overlay|"
+                                        th:text="#{general.note}"></button>
+                                    <a
+                                        th:href="@{|/submission/${submission.id}/download|}"
+                                        class="button p-min"
+                                        data-style="outlined"
+                                        th:text="#{general.download}"></a>
+                                    <button
+                                        th:if="${@authorizationService.canResubmitSubmission(submission.id)}"
+                                        class="button p-min"
+                                        data-style="outlined"
+                                        th:onclick="|jQuery.post('/submission/${submission.id}/resubmit'); this.disabled = true;|"
+                                        th:text="#{submission.resubmit}"></button>
+                                    <button
+                                        class="button p-min"
+                                        data-style="outlined"
+                                        th:data-dialog="|feedback-${submission.id}-overlay|"
+                                        th:text="#{submission.feedback}"></button>
+                                </div>
+                            </div>
+                        </div>
+                    </li>
+                </ul>
+            </div>
+
+            <div id="members" aria-labelledby="members-tab" hidden>
+                <ul role="list" class="divided list">
+                    <li
+                        th:each="member, iter : ${group.memberUsernames}"
+                        class="flex space-between pil-5 pbl-3">
+                        <a
+                            th:if="${@authorizationService.canViewPerson(member)}"
+                            class="link"
+                            th:href="@{|/person/username${'?'}username=${member}|}"
+                            th:text="${member}"></a>
+                        <span
+                            th:unless="${@authorizationService.canViewPerson(member)}"
+                            th:text="${member}"></span>
+                        <div class="flex gap-3">
+                            <button
+                                th:if="${not @authorizationService.isStudentInModule(module.id) && @authorizationService.canRemoveMemberFromGroup(group.id, member)}"
+                                class="button p-min"
+                                data-style="outlined"
+                                data-type="error"
+                                type="button"
+                                th:data-dialog="|remove-${iter.index}-overlay|">
+                                Remove from group
+                            </button>
+                        </div>
+                    </li>
+                </ul>
+            </div>
+
+            <div th:replace="~{group/note :: overlay}"></div>
+            <div th:replace="~{group/download_submissions :: overlay}"></div>
+            <div th:replace="~{group/add_member :: overlay}"></div>
+            <div th:replace="~{group/leave :: overlay}"></div>
+            <div th:each="member, iter : ${group.memberUsernames}">
+                <div th:replace="~{group/remove_member :: overlay}"></div>
+            </div>
+            <div
+                th:each="entry : ${group.submissionMap.entrySet()}"
+                th:with="assignment = ${entry.key}, submissions = ${entry.value}">
+                <div th:replace="~{assignment/submit :: overlay('group')}"></div>
+                <div th:each="submission, iter : ${submissions}">
+                    <div th:replace="~{submission/note :: overlay}"></div>
+                    <div th:replace="~{submission/feedback :: overlay('group')}"></div>
                 </div>
             </div>
-        </div>
-    </div>
-
-    <div th:replace="~{group/note :: overlay}"></div>
-    <div th:replace="~{group/download_submissions :: overlay}"></div>
-    <div th:replace="~{group/add_member :: overlay}"></div>
-    <div th:replace="~{group/leave :: overlay}"></div>
-    <div th:each="member, iter : ${group.memberUsernames}">
-        <div th:replace="~{group/remove_member :: overlay}"></div>
-    </div>
-    <div th:each="entry : ${group.submissionMap.entrySet()}" th:with="assignment = ${entry.key}, submissions = ${entry.value}">
-        <div th:replace="~{assignment/submit :: overlay('group')}"></div>
-        <div th:each="submission, iter : ${submissions}">
-            <div th:replace="~{submission/note :: overlay}"></div>
-            <div th:replace="~{submission/feedback :: overlay('group')}"></div>
-        </div>
-    </div>
-
-    <div th:replace="~{submission/feedback :: script}"></div>
-    <script>
-        let currentTab = "submissions"
-        function switchTab(to) {
-            if (to !== currentTab) {
-                document.getElementById("submissions").classList.toggle("hidden");
-                document.getElementById("members").classList.toggle("hidden");
-                document.getElementById("submissions-tab").classList.toggle("active");
-                document.getElementById("members-tab").classList.toggle("active");
-            }
-            currentTab = to;
-        }
-
-        function toggleCollapse(lId, aId) {
-            const list = $(`#${lId}`);
-            list.find(`.list_item:not([data-item="${aId}"])`).addClass("collapsed");
-            list.find(`[data-item="${aId}"]`).toggleClass("collapsed");
-        }
-    </script>
-
-</div>
-
-</body>
-</html>
\ No newline at end of file
+        </main>
+    </body>
+</html>
diff --git a/src/main/resources/templates/header.html b/src/main/resources/templates/header.html
index 9a7ee26a37a8a8470dc187ab798846bdca295d2d..505f3cf66c16ef123d20372fa1e652b939ee37c4 100644
--- a/src/main/resources/templates/header.html
+++ b/src/main/resources/templates/header.html
@@ -18,45 +18,51 @@
 
 */-->
 
-<header class="header">
-    <div class="header_content">
-
-        <a th:href="@{/}" class="header_logo">
-            <img th:src="@{/img/tudelft-logo.png}" height="40px"/>
-            <span>| Submit</span>
+<!DOCTYPE html>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <header class="header" layout:fragment="header">
+        <a th:href="@{/}" class="header__title">
+            <h1>Submit</h1>
         </a>
 
-        <div class="header_controls">
-            <div th:if="${@authorizationService.isAuthenticated()}" class="header_links">
-                <a th:href="@{/edition/enrolled}">Course editions</a>
-                <a th:href="@{/edition/open}">Enrol</a>
-                <a th:if="${@authorizationService.isAdmin()}" th:href="@{/announcement/global}">Announcements</a>
-            </div>
-            <div class="header_buttons">
-                <div th:if="${@authorizationService.isAuthenticated()}" th:remove="tag">
-                    <div class="header_notifications">
-                        <a id="bell" th:href="@{/notification/list}" class="header_icon"><i class="fas fa-bell"></i></a>
-                        <span id="notification-amount" class="hidden header_notifications_amount"></span>
-                    </div>
-                    <button id="cog" class="header_icon" onclick="toggleOverlay('settings-overlay')"><i class="fas fa-cog"></i></button>
-                    <a class="header_icon" th:href="@{/person}"><i class="fas fa-user"></i></a>
-                </div>
-
-                <div class="header_dropdown">
-                    <button class="header_icon"><i class="fas fa-globe"></i></button>
-                    <div class="header_dropdown_content">
-                        <a href="?lang=en">English</a>
-                    </div>
-                </div>
-
-                <a th:unless="${@authorizationService.isAuthenticated()}" class="header_icon" th:href="@{/auth/login}"><i class="fas fa-sign-in-alt"></i></a>
-                <form th:if="${@authorizationService.isAuthenticated()}" th:action="@{/auth/logout}" method="post" class="header_icon">
-                    <button type="submit"><i class="fas fa-sign-out-alt"></i></button>
-                </form>
-            </div>
+        <div th:if="${@authorizationService.isAuthenticated()}" class="header__links">
+            <ul>
+                <li><a th:href="@{/edition/enrolled}">Your Courses</a></li>
+                <li><a th:href="@{/edition/open}">Catalog</a></li>
+                <li>
+                    <a th:if="${@authorizationService.isAdmin()}" th:href="@{/announcement/global}">
+                        Announcements
+                    </a>
+                </li>
+            </ul>
+        </div>
+
+        <div class="user-menu menu-wrapper" th:if="${@authorizationService.isAuthenticated()}">
+            <button aria-controls="user-menu">
+                <span th:text="${@authorizationService.authPerson.displayName}"></span>
+                <span class="fa-solid fa-chevron-down"></span>
+            </button>
+            <ul id="user-menu" class="menu" aria-expanded="false" role="list">
+                <li><a th:href="@{/person}">Profile</a></li>
+                <li><a th:href="@{/notification/list}">Notifications</a></li>
+                <li class="flex vertical">
+                    <button data-dialog="settings-overlay">Settings</button>
+                </li>
+                <li>
+                    <form class="flex vertical" th:action="@{/auth/logout}" method="post">
+                        <button>Log out</button>
+                    </form>
+                </li>
+            </ul>
         </div>
 
-    </div>
-</header>
+        <div class="header__user" th:unless="${@authorizationService.isAuthenticated()}">
+            <a th:href="@{/auth/login}">Log in</a>
+        </div>
 
-<div th:replace="~{settings :: overlay}"></div>
+        <div th:replace="~{settings :: overlay}"></div>
+    </header>
+</html>
diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html
index 7192aeee64703877ba7dd2cd536dc01dcdbe7012..a3a2bd9aa1d54913e07f85226008dfa693c44b42 100644
--- a/src/main/resources/templates/index.html
+++ b/src/main/resources/templates/index.html
@@ -18,28 +18,28 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
-      layout:decorate="~{container}">
-<head>
-    <title th:text="#{general.app_name}"></title>
-</head>
-
-<body>
-
-<div layout:fragment="sidebar" class="hidden"></div>
-<div layout:fragment="breadcrumbs" class="hidden"></div>
-
-<div layout:fragment="content">
-
-    <h1 class="title">Welcome to submit</h1>
-
-    <p>To log in, click the button in the top right.</p>
-
-</div>
-
-<div layout:fragment="footer">
-    <div layout:replace="~{footer}"></div>
-</div>
-
-</body>
-</html>
\ No newline at end of file
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+    layout:decorate="~{container}">
+    <head>
+        <title th:text="#{general.app_name}"></title>
+    </head>
+
+    <body>
+        <div layout:fragment="content">
+            <h1 class="font-800 mb-5">Welcome to Submit</h1>
+
+            <h2 class="font-600 mb-1">How to use this system?</h2>
+
+            <p class="mb-7">
+                After you log in, you can enrol in a course. You can then submit assignments for the
+                courses you are enrolled in. Courses automatically grade your submissions and/or
+                require you to sign off your assignments.
+            </p>
+
+            <a th:href="@{/auth/login}" class="button">Click here to log in</a>
+        </div>
+    </body>
+</html>
diff --git a/src/main/resources/templates/layout.html b/src/main/resources/templates/layout.html
index 7527131d2b95f3bf3b27b4e50a58aa607dc4f02c..bee725e7662727ab974a4759011d86bbe53c088f 100644
--- a/src/main/resources/templates/layout.html
+++ b/src/main/resources/templates/layout.html
@@ -18,41 +18,70 @@
 
 */-->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<head>
-    <link rel="stylesheet" type="text/css" th:href="@{/css/main.css}"/>
-    <link rel="stylesheet" href="/webjars/font-awesome/css/all.min.css"/>
-    <link rel="stylesheet" type="text/css" href="/webjars/dropzone/dropzone.css"/>
-
-    <script src="/webjars/jquery/jquery.min.js" defer></script>
-    <script src="/webjars/js-cookie/js.cookie.js"></script>
-    <script src="/webjars/sockjs-client/sockjs.min.js"></script>
-    <script src="/webjars/stomp-websocket/stomp.min.js"></script>
-    <script src="/webjars/dropzone/dropzone.js"></script>
-    <script th:src="@{/js/main.js}"></script>
-    <script th:src="@{/js/notifications.js}"></script>
-    <script th:src="@{/js/announcements.js}"></script>
-
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
-    <meta name="viewport" content="width=device-width initial-scale=1">
-    <meta name="_csrf" th:content="${_csrf.token}"/>
-    <meta name="_csrf_header" th:content="${_csrf.headerName}"/>
-
-    <script defer>
-        window.addEventListener('DOMContentLoaded', function() {
-            const token = $("meta[name='_csrf']").attr("content");
-            const header = $("meta[name='_csrf_header']").attr("content");
-            $(document).ajaxSend(function(e, xhr) {
-                xhr.setRequestHeader(header, token);
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <head>
+        <link rel="stylesheet" href="/webjars/chihuahui/1.0.0/main.css" />
+        <link rel="stylesheet" href="/webjars/font-awesome/css/all.min.css" />
+
+        <script src="/webjars/jquery/jquery.min.js" defer></script>
+        <script src="/webjars/js-cookie/js.cookie.js"></script>
+        <script src="/webjars/sockjs-client/sockjs.min.js"></script>
+        <script src="/webjars/stomp-websocket/stomp.min.js"></script>
+        <script type="module" src="/webjars/chihuahui/1.0.0/components.js"></script>
+        <script src="/webjars/chihuahui/1.0.0/selectbox.js"></script>
+        <script src="/webjars/chihuahui/1.0.0/theme.js"></script>
+        <script th:src="@{/js/notifications.js}"></script>
+        <script th:src="@{/js/announcements.js}"></script>
+
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+        <meta name="viewport" content="width=device-width initial-scale=1" />
+        <meta name="_csrf" th:content="${_csrf.token}" />
+        <meta name="_csrf_header" th:content="${_csrf.headerName}" />
+
+        <script defer>
+            window.addEventListener("DOMContentLoaded", function () {
+                const token = $("meta[name='_csrf']").attr("content");
+                const header = $("meta[name='_csrf_header']").attr("content");
+                $(document).ajaxSend(function (e, xhr) {
+                    xhr.setRequestHeader(header, token);
+                });
             });
-        });
-    </script>
-</head>
-<body>
+        </script>
+
+        <style>
+            :root:not([data-theme]),
+            :root[data-theme="tu"] {
+                --primary-hue: 193deg;
+                --primary-saturation: 100%;
+
+                --header-colour: #00a6d6;
+                --primary-colour: #00a6d6;
+                --primary-active-colour: var(--primary-300);
+                --background-colour: #f7f7f7;
+            }
+
+            :root[data-theme="light"] {
+                --header-colour: #6810c1;
 
-<th:block layout:fragment="container">
+                --primary-hue: 270deg;
+                --primary-saturation: 90%;
 
-</th:block>
+                --background-colour: #f5f5f5;
+            }
+            :root[data-theme="dark"] {
+                --header-colour: #6810c1;
+                --primary-hue: 270deg;
+                --primary-saturation: 90%;
 
-</body>
+                --primary-colour: var(--primary-700);
+                --secondary-colour: var(--secondary-600);
+            }
+        </style>
+    </head>
+    <body>
+        <th:block layout:fragment="container"></th:block>
+    </body>
 </html>
diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html
index bc754744f0762ca548d816ef35852cce3102b668..6488d9e38b1c67b2d06846ac6b142dd12ff0d9c1 100644
--- a/src/main/resources/templates/login.html
+++ b/src/main/resources/templates/login.html
@@ -18,32 +18,30 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
-      layout:decorate="~{container}">
-<head>
-    <title th:text="#{general.login}"></title>
-</head>
-<body>
-
-<div layout:fragment="sidebar" class="hidden"></div>
-<div layout:fragment="breadcrumbs" class="hidden"></div>
-
-<div layout:fragment="content">
-
-    <h1 class="title" th:text="#{general.login}"></h1>
-    <form style="display: flex; flex-direction: column; max-width: 25rem" action="#" th:action="@{/perform-login}" method="post">
-        <div class="form-group">
-            <label for="username">Username</label>
-            <input type="text" class=textfield id="username" name="username">
-            <label for="password">Password</label>
-            <input type="password" class="textfield" id="password" name="password">
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+    layout:decorate="~{container}">
+    <head>
+        <title th:text="#{general.login}"></title>
+    </head>
+    <body>
+        <div layout:fragment="content">
+            <h1 class="font-800 mb-5" th:text="#{general.login}"></h1>
+
+            <form class="flex vertical" th:action="@{/perform-login}" method="post">
+                <div class="grid col-2 gap-3 align-center" style="--col-1: 5rem; --col-2: 15rem">
+                    <label for="username">Username</label>
+                    <input type="text" class="textfield" id="username" name="username" />
+                    <label for="password">Password</label>
+                    <input type="password" class="textfield" id="password" name="password" />
+                </div>
+
+                <div>
+                    <button class="button" type="submit" th:text="#{general.login}"></button>
+                </div>
+            </form>
         </div>
-
-        <div class="form-buttons">
-            <button class="text-button" type="submit" th:text="#{general.login}"></button>
-        </div>
-    </form>
-</div>
-
-</body>
-</html>
\ No newline at end of file
+    </body>
+</html>
diff --git a/src/main/resources/templates/module/add.html b/src/main/resources/templates/module/add.html
index ef18dfa4c1ce0f9315370cf29baea2dd3602ed66..ac5206e2d9d909d4d039b27b9f75ea3e8311a7ab 100644
--- a/src/main/resources/templates/module/add.html
+++ b/src/main/resources/templates/module/add.html
@@ -18,23 +18,45 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <dialog
+            th:fragment="overlay"
+            th:if="${@authorizationService.canCreateModule(edition.id)}"
+            id="add-module-overlay"
+            class="dialog">
+            <form
+                th:object="${module}"
+                th:action="@{/module}"
+                method="post"
+                class="flex vertical p-7">
+                <h2 class="underlined font-500" th:text="#{module.add}"></h2>
 
-<div th:fragment="overlay" th:if="${@authorizationService.canCreateModule(edition.id)}" id="add-module-overlay" class="hidden overlay">
-    <form th:object="${module}" th:action="@{/module}" method="post" class="boxed-content">
-        <h1 class="underlined title" th:text="#{module.add}"></h1>
-        <div class="form-group">
-            <input type="hidden" th:name="edition.id" th:value="${edition.id}"/>
-            <label for="module-name" th:text="#{general.name}"></label>
-            <input id="module-name" th:name="name" th:placeholder="#{general.name.enter}" type="text" class="textfield" required/>
-        </div>
-        <div class="form-buttons">
-            <button type=button class="warning text-button" th:text="#{general.cancel}" onclick="toggleOverlay('add-module-overlay')"></button>
-            <button type=submit class="text-button" th:text="#{general.save}"></button>
-        </div>
-    </form>
-</div>
+                <div class="grid col-2 gap-3 align-center" style="--col-1: minmax(0, 6rem)">
+                    <input type="hidden" th:name="edition.id" th:value="${edition.id}" />
+                    <label for="module-name" th:text="#{general.name}"></label>
+                    <input
+                        id="module-name"
+                        th:name="name"
+                        th:placeholder="#{general.name.enter}"
+                        type="text"
+                        class="textfield"
+                        required />
+                </div>
 
-</body>
-</html>
\ No newline at end of file
+                <div class="flex space-between">
+                    <button
+                        type="button"
+                        class="button p-less"
+                        data-style="outlined"
+                        data-cancel
+                        th:text="#{general.cancel}"></button>
+                    <button type="submit" class="button p-less" th:text="#{general.save}"></button>
+                </div>
+            </form>
+        </dialog>
+    </body>
+</html>
diff --git a/src/main/resources/templates/module/create_groups.html b/src/main/resources/templates/module/create_groups.html
index 1df242cc4d59d909f501d95814d934f17a50a00c..f559331db34d4d5f85c5c4915abaef5b1fdb0745 100644
--- a/src/main/resources/templates/module/create_groups.html
+++ b/src/main/resources/templates/module/create_groups.html
@@ -18,91 +18,91 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
-      layout:decorate="~{container}">
-<head>
-<!--    <link rel="stylesheet" type="text/css" th:href="@{/css/module.css}"/>-->
-
-    <title th:text="${module.name}"></title>
-
-    <script>
-        let currentTab = "allocate"
-        function switchTab(to) {
-            if (to !== currentTab) {
-                document.getElementById("allocate").classList.toggle("hidden")
-                document.getElementById("generate").classList.toggle("hidden")
-                document.getElementById("allocate-tab").classList.toggle("active")
-                document.getElementById("generate-tab").classList.toggle("active")
-            }
-            currentTab = to
-        }
-    </script>
-</head>
-
-<body>
-
-<div layout:fragment="breadcrumbs">
-    <a th:href="@{/edition/enrolled}" th:text="#{edition.editions}"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{|/edition/${module.edition.id}|}" th:text="|${edition.course.name} - ${edition.name}|"></a>
-    <div th:if="${@authorizationService.canViewModule(module.id)}" th:remove="tag">
-        <span>&nbsp;&gt;&nbsp;</span>
-        <a th:href="@{|/module/${module.id}|}" th:text="${module.name}"></a>
-        <span>&nbsp;&gt;&nbsp;</span>
-        <a th:href="@{|/module/${module.id}/groups|}" th:text="#{module.groups}"></a>
-    </div>
-    <div th:unless="${@authorizationService.canViewModule(module.id)}" th:remove="tag">
-        <span>&nbsp;&gt;&nbsp;</span>
-        <a th:href="@{|/module/${module.id}/groups|}" th:text="${module.name}"></a>
-    </div>
-</div>
-
-<div th:classappend="${@authorizationService.canViewModule(module.id)} ? '' : 'hidden'" layout:fragment="sidebar">
-    <a class="sidebar_item" th:href="@{|/module/${module.id}|}" th:text="#{general.details}"></a>
-    <a class="sidebar_item" th:href="@{|/module/${module.id}/groups|}" th:text="#{module.groups_and_grading}"></a>
-    <a th:if="${@authorizationService.canViewModuleOverview(module.id)}" class="sidebar_item" th:href="@{|/module/${module.id}/progress|}" th:text="#{general.progress_overview}"></a>
-</div>
-
-<div layout:fragment="content">
-
-    <h1 class="title" th:text="#{module.create_groups}"></h1>
-
-    <div class="tabs">
-        <button id="allocate-tab" class="active tab" th:text="#{module.create_groups.allocate}" onclick="switchTab('allocate')"></button>
-        <button id="generate-tab" class="tab" th:text="#{module.create_groups.generate}" onclick="switchTab('generate')"></button>
-    </div>
-
-    <div id="allocate">
-        <form style="display: flex; flex-direction: column; width: 30rem" th:action="@{|/group/${module.id}/allocate|}" method="post">
-            <div class="form-group">
-                <label for="allocate-capacity" th:text="#{group.capacity}"></label>
-                <input type="number" class=textfield id="allocate-capacity" name="capacity" min=1
-                       th:placeholder="#{group.capacity.enter}" required/>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+    layout:decorate="~{container}">
+    <head>
+        <title th:text="${module.name}"></title>
+    </head>
+
+    <body>
+        <main layout:fragment="content">
+            <th:block layout:replace="~{module/top :: top}"></th:block>
+
+            <div class="flex gap-3">
+                <button class="button" data-dialog="allocate">Allocate groups</button>
+                <button class="button" data-dialog="generate">Generate groups</button>
             </div>
-            <div class="form-buttons">
-                <button class="text-button" type="submit" th:text="#{module.create_groups}"></button>
-            </div>
-        </form>
-    </div>
-
-    <div id="generate" class="hidden">
-        <form style="display: flex; flex-direction: column; width: 30rem" th:object="${groupGen}"
-              th:action="@{|/group/${module.id}/generate|}" method="post">
-            <div class="form-group">
-                <label for="amount" th:text="#{module.create_groups.amount}"></label>
-                <input type="number" class=textfield id="amount" th:name="amount" min=1
-                       th:placeholder="#{module.create_groups.amount.enter}" required/>
-                <label for="generate-capacity" th:text="#{group.capacity}"></label>
-                <input type="number" class=textfield id="generate-capacity" th:name="capacity" min=1
-                       th:placeholder="#{group.capacity.enter}" required/>
-            </div>
-            <div class="form-buttons">
-                <button class="text-button" type="submit" th:text="#{module.create_groups}"></button>
-            </div>
-        </form>
-    </div>
-
-</div>
 
-</body>
-</html>
\ No newline at end of file
+            <dialog id="allocate" class="dialog">
+                <form
+                    class="flex vertical p-7"
+                    th:action="@{|/group/${module.id}/allocate|}"
+                    method="post">
+                    <h2 class="underlined font-500">Allocate groups</h2>
+                    <div class="grid col-2 align-center gap-3" style="--col-1: minmax(0, 8rem)">
+                        <label for="allocate-capacity" th:text="#{group.capacity}"></label>
+                        <input
+                            type="number"
+                            class="textfield"
+                            id="allocate-capacity"
+                            name="capacity"
+                            min="1"
+                            th:placeholder="#{group.capacity.enter}"
+                            required />
+                    </div>
+                    <div class="flex space-between">
+                        <button class="button p-less" data-style="outlined" data-cancel>
+                            Cancel
+                        </button>
+                        <button
+                            class="button p-less"
+                            type="submit"
+                            th:text="#{module.create_groups}"></button>
+                    </div>
+                </form>
+            </dialog>
+
+            <dialog id="generate" class="dialog">
+                <form
+                    th:object="${groupGen}"
+                    th:action="@{|/group/${module.id}/generate|}"
+                    class="flex vertical p-7"
+                    method="post">
+                    <h2 class="underlined font-500">Generate groups</h2>
+                    <div class="grid col-2 align-center gap-3" style="--col-1: minmax(0, 10rem)">
+                        <label for="amount" th:text="#{module.create_groups.amount}"></label>
+                        <input
+                            type="number"
+                            class="textfield"
+                            id="amount"
+                            th:name="amount"
+                            min="1"
+                            th:placeholder="#{module.create_groups.amount.enter}"
+                            required />
+                        <label for="generate-capacity" th:text="#{group.capacity}"></label>
+                        <input
+                            type="number"
+                            class="textfield"
+                            id="generate-capacity"
+                            th:name="capacity"
+                            min="1"
+                            th:placeholder="#{group.capacity.enter}"
+                            required />
+                    </div>
+                    <div class="flex space-between">
+                        <button class="button p-less" data-style="outlined" data-cancel>
+                            Cancel
+                        </button>
+                        <button
+                            class="button p-less"
+                            type="submit"
+                            th:text="#{module.create_groups}"></button>
+                    </div>
+                </form>
+            </dialog>
+        </main>
+    </body>
+</html>
diff --git a/src/main/resources/templates/module/edit.html b/src/main/resources/templates/module/edit.html
index 6c900d69a93ec3f1587dacabc9b683f2366f7934..033100fa21bfc2c0a74af6f1baaae13561894faf 100644
--- a/src/main/resources/templates/module/edit.html
+++ b/src/main/resources/templates/module/edit.html
@@ -18,22 +18,45 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <dialog
+            th:fragment="overlay"
+            th:if="${@authorizationService.canEditModule(module.id)}"
+            id="edit-overlay"
+            class="dialog">
+            <form
+                th:object="${module}"
+                th:action="@{|/module/${module.id}|}"
+                th:method="patch"
+                class="flex vertical p-7">
+                <h1 class="underlined font-500" th:text="#{module.edit}"></h1>
 
-<div th:fragment="overlay" th:if="${@authorizationService.canEditModule(module.id)}" id="edit-overlay" class="hidden overlay">
-    <form th:object="${module}" th:action="@{|/module/${module.id}|}" th:method="patch" class="boxed-content">
-        <h1 class="underlined title" th:text="#{module.edit}"></h1>
-        <div class="form-group">
-            <label for="module-name" th:text="#{general.name}"></label>
-            <input id="module-name" th:name="name" th:value="${module.name}" th:placeholder="#{general.name.enter}" type="text" class="textfield" required/>
-        </div>
-        <div class="form-buttons">
-            <button type=button class="warning text-button" th:text="#{general.cancel}" onclick="toggleOverlay('edit-overlay')"></button>
-            <button type=submit class="text-button" th:text="#{general.save}"></button>
-        </div>
-    </form>
-</div>
+                <div class="grid col-2 align-center" style="--col-1: minmax(0, 6rem)">
+                    <label for="module-name" th:text="#{general.name}"></label>
+                    <input
+                        id="module-name"
+                        th:name="name"
+                        th:value="${module.name}"
+                        th:placeholder="#{general.name.enter}"
+                        type="text"
+                        class="textfield"
+                        required />
+                </div>
 
-</body>
-</html>
\ No newline at end of file
+                <div class="flex space-between">
+                    <button
+                        type="button"
+                        class="button p-less"
+                        th:text="#{general.cancel}"
+                        data-style="outlined"
+                        data-cancel></button>
+                    <button type="submit" class="button p-less" th:text="#{general.save}"></button>
+                </div>
+            </form>
+        </dialog>
+    </body>
+</html>
diff --git a/src/main/resources/templates/module/grading.html b/src/main/resources/templates/module/grading.html
index 342db0883a3841929f1bad9083fbc57179756773..ab93f0f60085184ebcafeeff8fc9ef4a3519e994 100644
--- a/src/main/resources/templates/module/grading.html
+++ b/src/main/resources/templates/module/grading.html
@@ -18,82 +18,126 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <dialog
+            th:fragment="overlay"
+            th:if="${@authorizationService.canEditModuleGrading(module.id)}"
+            id="grading-overlay"
+            class="dialog">
+            <script>
+                function testFormula() {
+                    let formula = document.getElementById("formula").value;
+                    let type = document.getElementById("grade-type").value;
+                    let scores = {};
+                    for (let score of document.getElementById("test-fields").children) {
+                        scores[score.id.substring(5)] = score.value;
+                    }
+                    let testData = {
+                        formula: formula,
+                        type: type,
+                        scores: scores,
+                    };
+                    $.ajax({
+                        type: "POST",
+                        url: "/grade/test",
+                        data: JSON.stringify(testData),
+                        success: res => (document.getElementById("test-result").value = res.result),
+                        dataType: "json",
+                        contentType: "application/json",
+                    });
+                }
+            </script>
 
-<div th:fragment="overlay" th:if="${@authorizationService.canEditModuleGrading(module.id)}" id="grading-overlay" class="hidden overlay">
-    <script>
-        function testFormula() {
-            let formula = document.getElementById("formula").value;
-            let type = document.getElementById("grade-type").value;
-            let scores = {}
-            for (let score of document.getElementById("test-fields").children) {
-                scores[score.id.substring(5)] = score.value;
-            }
-            let testData = {
-                "formula": formula,
-                "type": type,
-                "scores": scores
-            };
-            $.ajax({
-                type: "POST",
-                url: "/grade/test",
-                data: JSON.stringify(testData),
-                success: res => document.getElementById("test-result").value = res.result,
-                dataType: "json",
-                contentType: "application/json"
-            });
-        }
-    </script>
-    <form th:object="${grading}" th:action="@{|/module/${module.id}/grading|}" class="boxed-content" th:method="patch">
-        <h1 class="underlined title" th:text="#{module.grading_formula}"></h1>
-        <div class="form-group">
+            <form
+                th:object="${grading}"
+                th:action="@{|/module/${module.id}/grading|}"
+                class="flex vertical p-7"
+                th:method="patch">
+                <h1 class="underlined font-500" th:text="#{module.grading_formula}"></h1>
 
-            <div>
-                <label for="formula" th:text="#{grading.formula}"></label>
-                <div><a th:href="@{/grade/help}" class="smaller text-button" th:text="#{general.help}"></a></div>
-            </div>
-            <input id="formula" th:name="formula" class="textfield" type="text"
-                   th:value="${module.gradingFormula?.formula}"
-                   th:placeholder="#{grading.formula.enter}"/>
+                <div class="grid col-2 align-center" style="--col-1: minmax(0, 12rem)">
+                    <div>
+                        <label for="formula" th:text="#{grading.formula}"></label>
+                        <div>
+                            <a th:href="@{/grade/help}" class="link" th:text="#{general.help}"></a>
+                        </div>
+                    </div>
+                    <div class="flex vertical gap-1">
+                        <input
+                            id="formula"
+                            th:name="formula"
+                            class="textfield"
+                            type="text"
+                            th:value="${module.gradingFormula?.formula}"
+                            th:placeholder="#{grading.formula.enter}" />
+                        <div class="font-200 pl-1">
+                            <span th:text="|#{grading.available_scores}:|"></span>
+                            <span
+                                th:text="${#strings.listJoin(module.assignments.![@gradeService.generateVariableName(#this.name)], ', ')}"></span>
+                        </div>
+                    </div>
 
-            <span th:text="#{grading.available_scores}"></span>
-            <div><span class="smaller" th:each="assignment, iter : ${module.assignments}" th:with="name = ${@gradeService.generateVariableName(assignment.name)}"
-                       th:text="${iter.count == module.assignments.size()} ? ${name} : ${name + ', '}"></span></div>
+                    <label for="grade-type" th:text="#{general.type}"></label>
+                    <select id="grade-type" th:name="type" class="textfield">
+                        <option
+                            th:each="type : ${T(nl.tudelft.labracore.lib.api.GradeScheme).values()}"
+                            th:selected="${type.name() == module.gradingFormula?.type?.name()}"
+                            th:value="${type}"
+                            th:text="#{|grading.type.${#strings.toLowerCase(type)}|}"></option>
+                    </select>
 
-            <label for="grade-type" th:text="#{general.type}"></label>
-            <select id="grade-type" th:name="type" class="selectbox">
-                <option th:each="type : ${T(nl.tudelft.labracore.lib.api.GradeScheme).values()}"
-                        th:selected="${type.name() == module.gradingFormula?.type?.name()}"
-                        th:value="${type}" th:text="#{|grading.type.${#strings.toLowerCase(type)}|}"></option>
-            </select>
+                    <div class="underlined" style="grid-column: span 2"></div>
 
-            <div class="form-separator"></div>
-
-            <div>
-                <span th:text="#{grading.test_formula}"></span>
-                <div class="tooltip">
-                    <span class="tooltip_icon">?</span>
-                    <p class="tooltip_text" th:text="#{grading.test_formula.help}"></p>
-                </div>
-            </div>
-            <div class="form-hstack">
-                <div th:id="test-fields" class="form-vstack">
-                    <input th:each="assignment : ${module.assignments}" th:with="name = ${@gradeService.generateVariableName(assignment.name)}" class="textfield" type="text"
-                           th:id="|test-${name}|" th:aria-label="|${assignment.name} test score|" th:placeholder="${assignment.name}"/>
+                    <div>
+                        <span th:text="#{grading.test_formula}"></span>
+                        <div class="tooltip">
+                            <span class="tooltip__control fa-solid fa-question"></span>
+                            <div role="tooltip" style="white-space: initial; min-width: 30rem">
+                                <p th:text="#{grading.test_formula.help}"></p>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="flex align-center">
+                        <div th:id="test-fields" class="flex vertical gap-3">
+                            <input
+                                th:each="assignment : ${module.assignments}"
+                                th:with="name = ${@gradeService.generateVariableName(assignment.name)}"
+                                class="textfield"
+                                type="text"
+                                th:id="|test-${name}|"
+                                th:aria-label="|${assignment.name} test score|"
+                                th:placeholder="${assignment.name}" />
+                        </div>
+                        <span>=</span>
+                        <input
+                            id="test-result"
+                            aria-label="Formula test result"
+                            class="textfield"
+                            type="text"
+                            readonly />
+                        <button
+                            class="button"
+                            data-style="outlined"
+                            type="button"
+                            th:text="#{grading.calculate}"
+                            onclick="testFormula()"></button>
+                    </div>
                 </div>
-                <span>=</span>
-                <input id="test-result" aria-label="Formula test result" class="textfield" type="text" readonly/>
-                <button class="text-button" type="button" th:text="#{grading.calculate}" onclick="testFormula()"></button>
-            </div>
-        </div>
-        <div class="form-buttons">
-            <button type=button class="warning text-button" th:text="#{general.cancel}"
-                    onclick="toggleOverlay('grading-overlay')"></button>
-            <button type=submit class="text-button" th:text="#{general.save}"></button>
-        </div>
-    </form>
-</div>
 
-</body>
-</html>
\ No newline at end of file
+                <div class="flex space-between">
+                    <button
+                        type="button"
+                        class="button p-less"
+                        data-style="outlined"
+                        th:text="#{general.cancel}"
+                        data-cancel></button>
+                    <button type="submit" class="button p-less" th:text="#{general.save}"></button>
+                </div>
+            </form>
+        </dialog>
+    </body>
+</html>
diff --git a/src/main/resources/templates/module/groups.html b/src/main/resources/templates/module/groups.html
index ccd0149f779540b6dc8a2f42368fe10435434df7..1ad00a277db538c07e67f078388b9adacfedc711 100644
--- a/src/main/resources/templates/module/groups.html
+++ b/src/main/resources/templates/module/groups.html
@@ -18,87 +18,93 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
-      layout:decorate="~{container}">
-<head>
-<!--    <link rel="stylesheet" type="text/css" th:href="@{/css/module.css}"/>-->
-
-    <title th:text="${module.name}"></title>
-</head>
-
-<body>
-
-<div layout:fragment="breadcrumbs">
-    <a th:href="@{/edition/enrolled}" th:text="#{edition.editions}"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{|/edition/${module.edition.id}|}" th:text="|${edition.course.name} - ${edition.name}|"></a>
-    <div th:if="${@authorizationService.canViewModule(module.id)}" th:remove="tag">
-        <span>&nbsp;&gt;&nbsp;</span>
-        <a th:href="@{|/module/${module.id}|}" th:text="${module.name}"></a>
-        <span>&nbsp;&gt;&nbsp;</span>
-        <a th:href="@{|/module/${module.id}/groups|}" th:text="#{module.groups}"></a>
-    </div>
-    <div th:unless="${@authorizationService.canViewModule(module.id)}" th:remove="tag">
-        <span>&nbsp;&gt;&nbsp;</span>
-        <a th:href="@{|/module/${module.id}/groups|}" th:text="${module.name}"></a>
-    </div>
-</div>
-
-<div th:classappend="${@authorizationService.canViewModule(module.id)} ? '' : 'hidden'" layout:fragment="sidebar">
-    <a class="sidebar_item" th:href="@{|/module/${module.id}|}" th:text="#{general.details}"></a>
-    <a class="sidebar_item" th:href="@{|/module/${module.id}/groups|}" th:text="#{module.groups_and_grading}"></a>
-    <div class="sidebar_group">
-        <a th:if="${@authorizationService.canViewModuleGroups(module.id)}" target="_blank" class="sidebar_item" th:href="@{|/module/${module.id}/export/groups|}" th:text="#{module.export_groups}"></a>
-    </div>
-    <a th:if="${@authorizationService.canViewModuleOverview(module.id)}" class="sidebar_item" th:href="@{|/module/${module.id}/progress|}" th:text="#{general.progress_overview}"></a>
-</div>
-
-<div layout:fragment="content">
-
-    <div th:with="edition = ${module.edition}" th:remove="tag">
-        <div th:replace="~{announcement/edition :: top}"></div>
-    </div>
-
-    <h1 class="title" th:text="${module.name}"></h1>
-
-    <div class="title-and-search">
-        <h2 class="subtitle" th:text="#{module.groups}"></h2>
-
-        <form th:if="${@authorizationService.canViewModule(module.id)}" id="group-filter" class="title-and-search_elements">
-            <select name="division" aria-label="Filter on division" class="selectbox" onchange="submitForm('group-filter')">
-                <option value="all" th:text="#{module.all_divisions}"></option>
-                <option th:each="division, iter : ${module.divisions}" th:text="#{division.name(${iter.count})}"
-                        th:value="${division.id}"
-                        th:selected="${param.division?.toString() == division.id.toString()}"></option>
-            </select>
-            <div class="search">
-                <input name="q" class="search_field" aria-label="Group search" type="search" th:value="${param.q}"
-                       th:placeholder="#{module.group_search}"/>
-                <button class="search_button" type="submit"><span class="fas fa-search"></span></button>
-            </div>
-        </form>
-    </div>
-
-    <table class="table">
-        <tr class="table_header">
-            <th th:text="#{general.name}"></th>
-            <th th:text="#{group.group_size}"></th>
-            <th></th>
-        </tr>
-        <tr th:each="group : ${groups}">
-            <td th:text="${group.name}"></td>
-            <td th:text="|${group.memberUsernames.size()} / ${group.capacity}|"></td>
-            <td><div class="table_actions">
-                <a th:if="${@authorizationService.canViewGroup(group.id)}" class="text-button" th:href="@{|/group/${group.id}|}" th:text="#{general.details}"></a>
-                <form th:action="@{|/group/${group.id}/join|}" method="post">
-                    <button type="submit" th:disabled="${group.memberUsernames.size() >= group.capacity}"
-                            class="text-button" th:text="#{general.join}"></button>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+    layout:decorate="~{container}">
+    <head>
+        <!--    <link rel="stylesheet" type="text/css" th:href="@{/css/module.css}"/>-->
+
+        <title th:text="${module.name}"></title>
+    </head>
+
+    <body>
+        <div layout:fragment="content">
+            <!--            <div class="sidebar_group">-->
+            <!--                <a-->
+            <!--                        th:if="${@authorizationService.canViewModuleGroups(module.id)}"-->
+            <!--                        target="_blank"-->
+            <!--                        class="sidebar_item"-->
+            <!--                        th:href="@{|/module/${module.id}/export/groups|}"-->
+            <!--                        th:text="#{module.export_groups}"></a>-->
+            <!--            </div>-->
+
+            <th:block layout:replace="~{module/top :: top}"></th:block>
+
+            <div class="flex vertical">
+                <form
+                    th:if="${@authorizationService.canViewModule(module.id)}"
+                    id="group-filter"
+                    class="flex gap-3">
+                    <select
+                        name="division"
+                        aria-label="Filter on division"
+                        class="textfield"
+                        onchange="document.getElementById('group-filter').submit()">
+                        <option value="all" th:text="#{module.all_divisions}"></option>
+                        <option
+                            th:each="division, iter : ${module.divisions}"
+                            th:text="#{division.name(${iter.count})}"
+                            th:value="${division.id}"
+                            th:selected="${param.division?.toString() == division.id.toString()}"></option>
+                    </select>
+                    <div class="search">
+                        <input
+                            name="q"
+                            class="search_field"
+                            aria-label="Group search"
+                            type="search"
+                            th:value="${param.q}"
+                            th:placeholder="#{module.group_search}" />
+                        <button class="search_button" type="submit">
+                            <span class="fas fa-search"></span>
+                        </button>
+                    </div>
                 </form>
-            </div></td>
-        </tr>
-    </table>
-
-</div>
 
-</body>
-</html>
\ No newline at end of file
+                <table class="table" data-style="surface">
+                    <tr class="table__header">
+                        <th th:text="#{general.name}"></th>
+                        <th th:text="#{group.group_size}"></th>
+                        <th></th>
+                    </tr>
+                    <tr th:each="group : ${groups}">
+                        <td>
+                            <a
+                                th:if="${@authorizationService.canViewGroup(group.id)}"
+                                class="link"
+                                th:href="@{/group/{id}(id=${group.id})}"
+                                th:text="${group.name}"></a>
+                            <span
+                                th:unless="${@authorizationService.canViewGroup(group.id)}"
+                                th:text="${group.name}"></span>
+                        </td>
+                        <td th:text="|${group.memberUsernames.size()} / ${group.capacity}|"></td>
+                        <td>
+                            <div class="flex gap-3 justify-end">
+                                <form th:action="@{|/group/${group.id}/join|}" method="post">
+                                    <button
+                                        type="submit"
+                                        th:disabled="${group.memberUsernames.size() >= group.capacity}"
+                                        class="button p-min"
+                                        th:text="#{general.join}"></button>
+                                </form>
+                            </div>
+                        </td>
+                    </tr>
+                </table>
+            </div>
+        </div>
+    </body>
+</html>
diff --git a/src/main/resources/templates/module/progress.html b/src/main/resources/templates/module/progress.html
index 5aabe18045fb5b7ad72330d49672621abde098cc..ab17b8d44eae39f3d8f0bc93c9454ba3860e3457 100644
--- a/src/main/resources/templates/module/progress.html
+++ b/src/main/resources/templates/module/progress.html
@@ -18,79 +18,69 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
-      layout:decorate="~{container}">
-<head>
-    <link rel="stylesheet" type="text/css" th:href="@{/css/progress.css}"/>
-
-    <title th:text="#{edition.student_overview}"></title>
-</head>
-
-<body>
-
-<div layout:fragment="breadcrumbs">
-    <a th:href="@{/edition/enrolled}" th:text="#{edition.editions}"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{|/edition/${module.edition.id}|}" th:text="|${edition.course.name} - ${edition.name}"|></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{|/edition/${module.edition.id}/students|}" th:text="#{edition.student_overview}"></a>
-</div>
-
-<div layout:fragment="sidebar" class="hidden"></div>
-
-<div layout:fragment="content">
-
-    <div th:with="edition = ${module.edition}" th:remove="tag">
-        <div th:replace="~{announcement/edition :: top}"></div>
-    </div>
-
-    <div class="title-and-search">
-        <h1 class="title" th:text="#{edition.students}"></h1>
-
-        <form class="search">
-            <input name="q" class="search_field" aria-label="Student search" type="search" th:value="${param.q}"
-                   th:placeholder="#{module.student_search}"/>
-            <button class="search_button" type="submit"><span class="fas fa-search"></span></button>
-        </form>
-    </div>
-
-    <div class="tabs">
-        <a th:href="@{|/module/${module.id}/progress|}" class="active tab" th:text="#{general.overview}">Overview</a>
-        <a th:href="@{|/edition/${module.edition.id}/students/actions|}" class="tab" th:text="#{general.actions}">Actions</a>
-    </div>
-
-    <table class="progress table">
-        <tr>
-            <td>
-                <a class="text-button" th:href="@{|/edition/${module.edition.id}/students|}" th:text="#{edition.student_overview.back}"></a>
-            </td>
-            <td th:each="assignment : ${module.assignments}">
-                <a class="text-button" th:href="@{|/assignment/${assignment.id}|}" th:text="${assignment.name}"></a>
-            </td>
-        </tr>
-        <tr class="table_header">
-            <th th:text="#{general.name}"></th>
-            <td th:each="assignment : ${module.assignments}">
-                <span th:text="#{edition.student_overview.percentage(${progress[assignment.id]})}"></span>
-            </td>
-        </tr>
-        <tr th:each="row : ${data}">
-            <td>
-                <a th:if="${@authorizationService.canViewPerson(row.person.id)}" class="text-button" th:href="@{|/person/${row.person.id}|}" th:text="${row.person.username}"></a>
-                <span th:unless="${@authorizationService.canViewPerson(row.person.id)}" th:text="${row.person.username}"></span>
-            </td>
-            <td th:each="assignment : ${module.assignments}"
-                th:classappend="${row.submissions[assignment.id] == null} ? '' :
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+    layout:decorate="~{container}">
+    <head>
+        <link rel="stylesheet" type="text/css" th:href="@{/css/progress.css}" />
+
+        <title th:text="#{edition.student_overview}"></title>
+    </head>
+
+    <body>
+        <main layout:fragment="content">
+            <th:block layout:replace="~{module/top :: top}"></th:block>
+
+            <div class="flex vertical">
+                <form class="search">
+                    <input
+                        name="q"
+                        class="search_field"
+                        aria-label="Student search"
+                        type="search"
+                        th:value="${param.q}"
+                        th:placeholder="#{module.student_search}" />
+                    <button class="fa-solid fa-search" type="submit"></button>
+                </form>
+
+                <table class="table">
+                    <tr class="table__header">
+                        <th th:text="#{general.name}"></th>
+                        <td th:each="assignment : ${module.assignments}">
+                            <a
+                                class="link"
+                                th:href="@{|/assignment/${assignment.id}|}"
+                                th:text="${assignment.name}"></a>
+                            <span
+                                th:text="|(#{edition.student_overview.percentage(${progress[assignment.id]})})|"></span>
+                        </td>
+                    </tr>
+                    <tr th:each="row : ${data}">
+                        <td>
+                            <a
+                                th:if="${@authorizationService.canViewPerson(row.person.id)}"
+                                class="text-button"
+                                th:href="@{|/person/${row.person.id}|}"
+                                th:text="${row.person.displayName}"></a>
+                            <span
+                                th:unless="${@authorizationService.canViewPerson(row.person.id)}"
+                                th:text="${row.person.displayName}"></span>
+                        </td>
+                        <td
+                            th:each="assignment : ${module.assignments}"
+                            th:classappend="${row.submissions[assignment.id] == null} ? '' :
                                 (${row.submissions[assignment.id].getMaxGrade() == null} ? 'submitted' :
                                 (${@gradeService.isPassing(row.submissions[assignment.id].getMaxGrade(), module.edition.id) == false} ? 'fail' : 'pass'))">
-                <span th:text="${row.submissions[assignment.id] == null} ? #{assignment.no_submission} :
+                            <span
+                                th:text="${row.submissions[assignment.id] == null} ? #{assignment.no_submission} :
                                (${row.submissions[assignment.id].getMaxGrade() == null} ? #{grading.no_grade} :
                                ${@gradeService.getScoreDisplayString(row.submissions[assignment.id].getMaxGrade())})"></span>
-            </td>
-        </tr>
-    </table>
-
-</div>
-
-</body>
-</html>
\ No newline at end of file
+                        </td>
+                    </tr>
+                </table>
+            </div>
+        </main>
+    </body>
+</html>
diff --git a/src/main/resources/templates/module/statistics.html b/src/main/resources/templates/module/statistics.html
index c94c7173429f694fbf64da20b2db63ed142b0f2e..84001435e5a0a7167e4e86b9cad0bffb325f519b 100644
--- a/src/main/resources/templates/module/statistics.html
+++ b/src/main/resources/templates/module/statistics.html
@@ -18,77 +18,53 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
-      layout:decorate="~{container}">
-<head>
-    <script src="/webjars/chartjs/Chart.min.js"></script>
-
-    <title th:text="#{general.statistics}"></title>
-</head>
-
-<body>
-
-<div layout:fragment="breadcrumbs">
-    <a th:href="@{/edition/enrolled}" th:text="#{edition.editions}"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{|/edition/${module.edition.id}|}" th:text="|${edition.course.name} - ${edition.name}|"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{|/module/${module.id}|}" th:text="${module.name}"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{|/module/${module.id}/statistics|}" th:text="#{general.statistics}"></a>
-</div>
-
-<div layout:fragment="sidebar">
-    <a class="sidebar_item" th:href="@{|/module/${module.id}|}" th:text="#{general.details}"></a>
-    <div class="sidebar_group">
-        <a th:if="${@authorizationService.canViewModuleStatistics(module.id)}" class="sidebar_item" th:href="@{|/module/${module.id}/statistics|}" th:text="#{general.statistics}"></a>
-    </div>
-    <a th:if="${@authorizationService.canViewModuleGroups(module.id)}" class="sidebar_item" th:href="@{|/module/${module.id}/groups|}" th:text="#{module.groups_and_grading}"></a>
-    <a th:if="${@authorizationService.canViewModuleOverview(module.id)}" class="sidebar_item" th:href="@{|/module/${module.id}/progress|}" th:text="#{general.progress_overview}"></a>
-</div>
-
-<div layout:fragment="content">
-
-    <div th:with="edition = ${module.edition}" th:remove="tag">
-        <div th:replace="~{announcement/edition :: top}"></div>
-    </div>
-
-    <h1 class="title" th:text="${module.name}"></h1>
-
-    <div class="tabs">
-        <a th:href="@{|/module/${module.id}/statistics|}" class="active tab" th:text="#{general.statistics}"></a>
-        <a th:href="@{|/module/${module.id}/statistics-table|}" class="tab" th:text="#{general.table}"></a>
-    </div>
-
-    <div class="info_row">
-        <div class="card_stats card">
-            <div class="card_stats_row">
-                <span th:text="#{statistics.total_students}"></span>
-                <span th:text="${statistics.totalStudents}"></span>
-            </div>
-            <div class="card_stats_row">
-                <span th:text="#{statistics.total_groups}"></span>
-                <span th:text="${statistics.totalGroups}"></span>
-            </div>
-        </div>
-        <div th:if="${statistics.averageScore != null or statistics.medianScore != null}" class="card_stats card">
-            <div th:if="${statistics.averageScore != null}" class="card_stats_row">
-                <span th:text="#{statistics.average_score}"></span>
-                <span th:text="${statistics.averageScore}"></span>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+    layout:decorate="~{container}">
+    <head>
+        <script src="/webjars/chartjs/Chart.min.js"></script>
+
+        <title th:text="#{general.statistics}"></title>
+    </head>
+
+    <body>
+        <div layout:fragment="content">
+            <div layout:replace="~{module/top :: top}"></div>
+
+            <div class="surface">
+                <div class="flex vertical gap-3">
+                    <div class="underlined flex space-between pil-3">
+                        <span th:text="#{statistics.total_students}"></span>
+                        <span th:text="${statistics.totalStudents}"></span>
+                    </div>
+                    <div class="underlined flex space-between pil-3">
+                        <span th:text="#{statistics.total_groups}"></span>
+                        <span th:text="${statistics.totalGroups}"></span>
+                    </div>
+                </div>
+                <th:block
+                    th:if="${statistics.averageScore != null or statistics.medianScore != null}">
+                    <div
+                        th:if="${statistics.averageScore != null}"
+                        class="underlined flex space-between pil-3">
+                        <span th:text="#{statistics.average_score}"></span>
+                        <span th:text="${statistics.averageScore}"></span>
+                    </div>
+                    <div
+                        th:if="${statistics.medianScore != null}"
+                        class="underlined flex space-between pil-3">
+                        <span th:text="#{statistics.median_score}"></span>
+                        <span th:text="${statistics.medianScore}"></span>
+                    </div>
+                </th:block>
             </div>
-            <div th:if="${statistics.medianScore != null}" class="card_stats_row">
-                <span th:text="#{statistics.median_score}"></span>
-                <span th:text="${statistics.medianScore}"></span>
+
+            <div class="grid col-2">
+                <div th:replace="~{statistics/view :: pass-fail}"></div>
+                <div th:replace="~{statistics/view :: score-distribution}"></div>
             </div>
         </div>
-    </div>
-
-    <div class="info_row">
-        <div th:replace="~{statistics/view :: pass-fail}"></div>
-        <div th:replace="~{statistics/view :: score-distribution}"></div>
-    </div>
-
-</div>
-
-</body>
+    </body>
 </html>
diff --git a/src/main/resources/templates/module/statistics_table.html b/src/main/resources/templates/module/statistics_table.html
index d247199c71889daa1e013bcca4e456f5daa2de76..729cdb5af592ba22fbe64bb00a3ecf968eec9346 100644
--- a/src/main/resources/templates/module/statistics_table.html
+++ b/src/main/resources/templates/module/statistics_table.html
@@ -18,79 +18,124 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
-      layout:decorate="~{container}">
-<head>
-    <title th:text="#{general.statistics}"></title>
-</head>
-
-<body>
-
-<div layout:fragment="breadcrumbs">
-    <a th:href="@{/edition/enrolled}" th:text="#{edition.editions}"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{|/edition/${module.edition.id}|}" th:text="|${edition.course.name} - ${edition.name}|"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{|/module/${module.id}|}" th:text="${module.name}"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{|/module/${module.id}/statistics|}" th:text="#{general.statistics}"></a>
-</div>
-
-<div layout:fragment="sidebar">
-    <a class="sidebar_item" th:href="@{|/module/${module.id}|}" th:text="#{general.details}"></a>
-    <div class="sidebar_group">
-        <a th:if="${@authorizationService.canViewModuleStatistics(module.id)}" class="sidebar_item" th:href="@{|/module/${module.id}/statistics|}" th:text="#{general.statistics}"></a>
-    </div>
-    <a th:if="${@authorizationService.canViewModuleGroups(module.id)}" class="sidebar_item" th:href="@{|/module/${module.id}/groups|}" th:text="#{module.groups_and_grading}"></a>
-    <a th:if="${@authorizationService.canViewModuleOverview(module.id)}" class="sidebar_item" th:href="@{|/module/${module.id}/progress|}" th:text="#{general.progress_overview}"></a>
-</div>
-
-<div layout:fragment="content">
-
-    <div th:with="edition = ${module.edition}" th:remove="tag">
-        <div th:replace="~{announcement/edition :: top}"></div>
-    </div>
-
-    <h1 class="title" th:text="${module.name}"></h1>
-
-    <div class="tabs">
-        <a th:href="@{|/module/${module.id}/statistics|}" class="tab" th:text="#{general.statistics}"></a>
-        <a th:href="@{|/module/${module.id}/statistics-table|}" class="active tab" th:text="#{general.table}"></a>
-    </div>
-
-    <div class="title-and-search">
-        <h2 class="subtitle"></h2>
-
-        <form id="filter-form" class="title-and-search_elements">
-            <select name="status" aria-label="Filter on status" class="selectbox" onchange="$('#filter-form').submit()">
-                <option value="all" th:text="#{grading.any_status}"></option>
-                <option th:each="status : ${T(nl.tudelft.submit.enums.GradeStatistics).values()}" th:selected="${#strings.toString(param.status) == status.name()}" th:value="${status.name()}" th:text="#{|grading.${#strings.toLowerCase(status.name())}|}"></option>
-            </select>
-            <div class="search">
-                <input name="q" class="search_field" th:value="${param.q}" aria-label="Student search" type="search" th:placeholder="#{module.student_search}"/>
-                <button class="search_button" type="submit"><span class="fas fa-search"></span></button>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+    layout:decorate="~{container}">
+    <head>
+        <title th:text="#{general.statistics}"></title>
+    </head>
+
+    <body>
+        <div layout:fragment="breadcrumbs">
+            <a th:href="@{/edition/enrolled}" th:text="#{edition.editions}"></a>
+            <span>&nbsp;&gt;&nbsp;</span>
+            <a
+                th:href="@{|/edition/${module.edition.id}|}"
+                th:text="|${edition.course.name} - ${edition.name}|"></a>
+            <span>&nbsp;&gt;&nbsp;</span>
+            <a th:href="@{|/module/${module.id}|}" th:text="${module.name}"></a>
+            <span>&nbsp;&gt;&nbsp;</span>
+            <a th:href="@{|/module/${module.id}/statistics|}" th:text="#{general.statistics}"></a>
+        </div>
+
+        <div layout:fragment="sidebar">
+            <a
+                class="sidebar_item"
+                th:href="@{|/module/${module.id}|}"
+                th:text="#{general.details}"></a>
+            <div class="sidebar_group">
+                <a
+                    th:if="${@authorizationService.canViewModuleStatistics(module.id)}"
+                    class="sidebar_item"
+                    th:href="@{|/module/${module.id}/statistics|}"
+                    th:text="#{general.statistics}"></a>
+            </div>
+            <a
+                th:if="${@authorizationService.canViewModuleGroups(module.id)}"
+                class="sidebar_item"
+                th:href="@{|/module/${module.id}/groups|}"
+                th:text="#{module.groups_and_grading}"></a>
+            <a
+                th:if="${@authorizationService.canViewModuleOverview(module.id)}"
+                class="sidebar_item"
+                th:href="@{|/module/${module.id}/progress|}"
+                th:text="#{general.progress_overview}"></a>
+        </div>
+
+        <div layout:fragment="content">
+            <div th:with="edition = ${module.edition}" th:remove="tag">
+                <div th:replace="~{announcement/edition :: top}"></div>
             </div>
-        </form>
-    </div>
 
-    <table class="table">
-        <tr class="table_header">
-            <th th:text="#{role.student}"></th>
-            <th th:text="#{group.group}"></th>
-            <th th:text="#{grading.grade}"></th>
-            <th></th>
-        </tr>
-        <tr th:each="row : ${data}">
-            <td th:text="${row.person.username}"></td>
-            <td>
-                <a th:if="${row.group != null and @authorizationService.canViewGroup(row.group.id)}" class="text-button" th:href="@{|/group/${row.group.id}|}" th:text="${row.group.name}"></a>
-                <span th:unless="${row.group != null and @authorizationService.canViewGroup(row.group.id)}" th:text="${row.group?.name} ?: #{module.no_group}"></span>
-            </td>
-            <td th:text="${row.grade == null} ? #{grading.no_grade} : ${@gradeService.getScoreDisplayString(row.grade)}"></td>
-        </tr>
-    </table>
+            <h1 class="title" th:text="${module.name}"></h1>
+
+            <div class="tabs">
+                <a
+                    th:href="@{|/module/${module.id}/statistics|}"
+                    class="tab"
+                    th:text="#{general.statistics}"></a>
+                <a
+                    th:href="@{|/module/${module.id}/statistics-table|}"
+                    class="active tab"
+                    th:text="#{general.table}"></a>
+            </div>
 
-</div>
+            <div class="title-and-search">
+                <h2 class="subtitle"></h2>
+
+                <form id="filter-form" class="title-and-search_elements">
+                    <select
+                        name="status"
+                        aria-label="Filter on status"
+                        class="selectbox"
+                        onchange="$('#filter-form').submit()">
+                        <option value="all" th:text="#{grading.any_status}"></option>
+                        <option
+                            th:each="status : ${T(nl.tudelft.submit.enums.GradeStatistics).values()}"
+                            th:selected="${#strings.toString(param.status) == status.name()}"
+                            th:value="${status.name()}"
+                            th:text="#{|grading.${#strings.toLowerCase(status.name())}|}"></option>
+                    </select>
+                    <div class="search">
+                        <input
+                            name="q"
+                            class="search_field"
+                            th:value="${param.q}"
+                            aria-label="Student search"
+                            type="search"
+                            th:placeholder="#{module.student_search}" />
+                        <button class="search_button" type="submit">
+                            <span class="fas fa-search"></span>
+                        </button>
+                    </div>
+                </form>
+            </div>
 
-</body>
-</html>
\ No newline at end of file
+            <table class="table">
+                <tr class="table_header">
+                    <th th:text="#{role.student}"></th>
+                    <th th:text="#{group.group}"></th>
+                    <th th:text="#{grading.grade}"></th>
+                    <th></th>
+                </tr>
+                <tr th:each="row : ${data}">
+                    <td th:text="${row.person.username}"></td>
+                    <td>
+                        <a
+                            th:if="${row.group != null and @authorizationService.canViewGroup(row.group.id)}"
+                            class="text-button"
+                            th:href="@{|/group/${row.group.id}|}"
+                            th:text="${row.group.name}"></a>
+                        <span
+                            th:unless="${row.group != null and @authorizationService.canViewGroup(row.group.id)}"
+                            th:text="${row.group?.name} ?: #{module.no_group}"></span>
+                    </td>
+                    <td
+                        th:text="${row.grade == null} ? #{grading.no_grade} : ${@gradeService.getScoreDisplayString(row.grade)}"></td>
+                </tr>
+            </table>
+        </div>
+    </body>
+</html>
diff --git a/src/main/resources/templates/module/top.html b/src/main/resources/templates/module/top.html
new file mode 100644
index 0000000000000000000000000000000000000000..d62e2264521c4401773a95ad7a477f704cec310f
--- /dev/null
+++ b/src/main/resources/templates/module/top.html
@@ -0,0 +1,92 @@
+<!--
+
+    Submit
+    Copyright (C) 2020 - Delft University of Technology
+
+    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 <https://www.gnu.org/licenses/>.
+
+-->
+<!DOCTYPE html>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <div layout:fragment="top">
+            <div class="breadcrumbs mb-5">
+                <a th:href="@{/edition/enrolled}" th:text="#{edition.editions}"></a>
+                <span>&gt;</span>
+                <a
+                    th:href="@{|/edition/${module.edition.id}|}"
+                    th:text="|${edition.course.name} - ${edition.name}|"></a>
+                <span>&gt;</span>
+                <a th:href="@{|/module/${module.id}|}" th:text="${module.name}"></a>
+            </div>
+
+            <div th:with="edition = ${module.edition}" th:remove="tag">
+                <div layout:replace="~{announcement/edition :: top}"></div>
+            </div>
+
+            <h1 class="font-800 mb-3" th:text="${module.name}"></h1>
+
+            <div class="flex gap-3 wrap mb-5 | sm:vertical">
+                <button
+                    th:if="${@authorizationService.canEditModule(module.id)}"
+                    class="button"
+                    data-style="outlined"
+                    th:text="#{module.edit}"
+                    data-dialog="edit-overlay"></button>
+                <button
+                    th:if="${@authorizationService.canEditModuleGrading(module.id)}"
+                    class="button"
+                    data-style="outlined"
+                    th:text="#{grading.grading}"
+                    data-dialog="grading-overlay"></button>
+            </div>
+
+            <div
+                class="tabs mb-5"
+                role="tablist"
+                th:unless="${@authorizationService.isStudentInEdition(edition.id)}">
+                <a
+                    role="tab"
+                    th:aria-selected="${#request.requestURI.matches('.*module/[0-9]+')}"
+                    th:if="${@authorizationService.canViewModule(module.id)}"
+                    th:href="@{|/module/${module.id}|}"
+                    th:text="#{general.overview}"></a>
+                <a
+                    role="tab"
+                    th:aria-selected="${#request.requestURI.matches('.*/groups.*')}"
+                    th:if="${@authorizationService.canViewModuleGroups(module.id)}"
+                    th:href="@{|/module/${module.id}/groups|}"
+                    th:text="#{module.groups_and_grading}"></a>
+                <a
+                    role="tab"
+                    th:aria-selected="${#request.requestURI.matches('.*/progress')}"
+                    th:if="${@authorizationService.canViewModuleOverview(module.id)}"
+                    th:href="@{|/module/${module.id}/progress|}"
+                    th:text="#{general.progress_overview}"></a>
+                <a
+                    role="tab"
+                    th:aria-selected="${#request.requestURI.matches('.*/statistics')}"
+                    th:if="${@authorizationService.canViewModuleStatistics(module.id)}"
+                    th:href="@{|/module/${module.id}/statistics|}"
+                    th:text="#{general.statistics}"></a>
+            </div>
+
+            <div th:replace="~{module/edit :: overlay}"></div>
+            <div th:replace="~{module/grading :: overlay}"></div>
+        </div>
+    </body>
+</html>
diff --git a/src/main/resources/templates/module/view.html b/src/main/resources/templates/module/view.html
index accfca651bfe0d970519eee900a480ecfa86ca63..8a36a0bb77a9235e7f2f064f5786d28b1cf58900 100644
--- a/src/main/resources/templates/module/view.html
+++ b/src/main/resources/templates/module/view.html
@@ -18,107 +18,72 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
-      layout:decorate="~{container}">
-<head>
-<!--    <link rel="stylesheet" type="text/css" th:href="@{/css/module.css}"/>-->
-
-    <title th:text="${module.name}"></title>
-</head>
-
-<body>
-
-<div layout:fragment="breadcrumbs">
-    <a th:href="@{/edition/enrolled}" th:text="#{edition.editions}"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{|/edition/${module.edition.id}|}" th:text="|${edition.course.name} - ${edition.name}|"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{|/module/${module.id}|}" th:text="${module.name}"></a>
-</div>
-
-<div layout:fragment="sidebar">
-    <a class="sidebar_item" th:href="@{|/module/${module.id}|}" th:text="#{general.details}"></a>
-    <div class="sidebar_group">
-        <button th:if="${@authorizationService.canEditModuleGrading(module.id)}" class="sidebar_item" th:text="#{grading.grading}" onclick="toggleOverlay('grading-overlay')"></button>
-        <a th:if="${@authorizationService.canViewModuleStatistics(module.id)}" class="sidebar_item" th:href="@{|/module/${module.id}/statistics|}" th:text="#{general.statistics}"></a>
-        <button th:if="${@authorizationService.canEditModule(module.id)}" class="sidebar_item" th:text="#{module.edit}" onclick="toggleOverlay('edit-overlay')"></button>
-    </div>
-    <a th:if="${@authorizationService.canViewModuleGroups(module.id)}" class="sidebar_item" th:href="@{|/module/${module.id}/groups|}" th:text="#{module.groups_and_grading}"></a>
-    <a th:if="${@authorizationService.canViewModuleOverview(module.id)}" class="sidebar_item" th:href="@{|/module/${module.id}/progress|}" th:text="#{general.progress_overview}"></a>
-</div>
-
-<div layout:fragment="content">
-
-    <div th:with="edition = ${module.edition}" th:remove="tag">
-        <div th:replace="~{announcement/edition :: top}"></div>
-    </div>
-
-    <h1 class="title" th:text="${module.name}"></h1>
-
-    <div class="info_row">
-        <div class="card_stats card">
-            <div class="smaller card_stats_row">
-                <span th:text="#{assignment.upcoming_deadline}"></span>
-                <span th:text="${upcoming == null} ? #{assignment.no_upcoming_deadline} : ${#temporals.format(upcoming.deadline, 'dd-MM-yyyy HH:mm')}"></span>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+    layout:decorate="~{container}">
+    <head>
+        <!--    <link rel="stylesheet" type="text/css" th:href="@{/css/module.css}"/>-->
+
+        <title th:text="${module.name}"></title>
+    </head>
+
+    <body>
+        <main layout:fragment="content">
+            <th:block layout:replace="~{module/top :: top}"></th:block>
+
+            <div class="surface mb-5">
+                <div class="flex vertical">
+                    <div class="underlined flex space-between pil-3">
+                        <span th:text="#{assignment.upcoming_deadline}"></span>
+                        <span
+                            th:text="${upcoming == null} ? #{assignment.no_upcoming_deadline} : ${#temporals.format(upcoming.deadline, 'dd-MM-yyyy HH:mm')}"></span>
+                    </div>
+                    <!--            <div class="smaller card_stats_row">-->
+                    <!--                <span th:text="#{submission.amount}"></span>-->
+                    <!--                <span th:text="${totalSubmissions}"></span> TODO make fast-->
+                    <!--            </div>-->
+                    <!--            <div class="smaller card_stats_row">-->
+                    <!--                <span th:text="#{assignment.students_finished}"></span>-->
+                    <!--                <span th:text="${totalFinished}"></span> TODO make fast-->
+                    <!--            </div>-->
+                </div>
             </div>
-<!--            <div class="smaller card_stats_row">-->
-<!--                <span th:text="#{submission.amount}"></span>-->
-<!--                <span th:text="${totalSubmissions}"></span> TODO make fast-->
-<!--            </div>-->
-<!--            <div class="smaller card_stats_row">-->
-<!--                <span th:text="#{assignment.students_finished}"></span>-->
-<!--                <span th:text="${totalFinished}"></span> TODO make fast-->
-<!--            </div>-->
-        </div>
-
-        <button th:if="${@authorizationService.canCreateAssignment(module.id)}" onclick="toggleOverlay('add-assignment-overlay')" class="card interactive">
-            <span class="card_icon fas fa-plus-circle"></span>
-            <span th:text="#{assignment.add}"></span>
-        </button>
-    </div>
 
-    <div class="tabs">
-        <button id="assignments-tab" class="active tab" th:text="#{module.assignments}" onclick="switchTab('assignments')"></button>
-        <button id="divisions-tab" class="tab" th:text="#{module.divisions}" onclick="switchTab('divisions')"></button>
-    </div>
-
-    <div id="assignments" class="list">
-        <div th:each="assignment : ${#lists.sort(module.assignments, @templateService.nameComparator)}" class="list_item">
-            <span class="list_item_content" th:text="${assignment.name}"></span>
-            <div class="list_actions">
-                <a th:if="${@authorizationService.canViewAssignment(assignment.id)}" class="text-button" th:href="@{|/assignment/${assignment.id}|}" th:text="#{general.details}"></a>
-                <a th:if="${@authorizationService.canViewAssignmentSubmissions(assignment.id)}" class="text-button" th:href="@{|/assignment/${assignment.id}/submissions|}" th:text="#{assignment.submissions}"></a>
-            </div>
-        </div>
-    </div>
+            <div class="flex space-between align-center mb-3">
+                <h2 class="font-500" th:text="#{module.assignments}"></h2>
 
-    <div id="divisions" class="hidden list">
-        <div th:each="division, iter : ${#lists.sort(module.divisions, @templateService.nameComparator)}" class="list_item">
-            <span class="list_item_content" th:text="#{division.name(${iter.count})}"></span>
-            <div class="list_actions">
-                <a th:if="${@authorizationService.canViewModuleGroups(module.id)}" class="text-button" th:href="@{|/module/${module.id}/groups${'?'}division=${division.id}|}" th:text="#{general.details}"></a>
+                <button
+                    th:if="${@authorizationService.canCreateAssignment(module.id)}"
+                    data-dialog="add-assignment-overlay"
+                    class="button"
+                    data-style="outlined">
+                    <span th:text="#{assignment.add}"></span>
+                </button>
             </div>
-        </div>
-    </div>
-
-    <div th:replace="~{module/edit :: overlay}"></div>
-    <div th:replace="~{module/grading :: overlay}"></div>
-    <div th:replace="~{assignment/add :: overlay}"></div>
-
-    <script>
-        let currentTab = "assignments"
-        function switchTab(to) {
-            if (to !== currentTab) {
-                document.getElementById("assignments").classList.toggle("hidden")
-                document.getElementById("divisions").classList.toggle("hidden")
-                document.getElementById("assignments-tab").classList.toggle("active")
-                document.getElementById("divisions-tab").classList.toggle("active")
-            }
-            currentTab = to
-        }
-    </script>
-
-</div>
 
-</body>
-</html>
\ No newline at end of file
+            <ul id="assignments" class="divided list" role="list">
+                <li
+                    class="flex space-between pil-5 pbl-3"
+                    th:each="assignment : ${#lists.sort(module.assignments, @templateService.nameComparator)}">
+                    <a
+                        th:if="${@authorizationService.canViewAssignment(assignment.id)}"
+                        class="link"
+                        th:href="@{/assignment/{id}(id=${assignment.id})}"
+                        th:text="${assignment.name}"></a>
+                    <span
+                        th:unless="${@authorizationService.canViewAssignment(assignment.id)}"
+                        th:text="${assignment.name}"></span>
+                    <a
+                        th:if="${@authorizationService.canViewAssignmentSubmissions(assignment.id)}"
+                        class="link"
+                        th:href="@{|/assignment/${assignment.id}/submissions|}"
+                        th:text="#{assignment.submissions}"></a>
+                </li>
+            </ul>
+
+            <div th:replace="~{assignment/add :: overlay}"></div>
+        </main>
+    </body>
+</html>
diff --git a/src/main/resources/templates/notifications.html b/src/main/resources/templates/notifications.html
index f68b4666247b599d9ec12e675f0e0c88a78e21cd..4e0dd5f644f805cf40c57d04d0d245b71cecd902 100644
--- a/src/main/resources/templates/notifications.html
+++ b/src/main/resources/templates/notifications.html
@@ -18,46 +18,48 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
-      layout:decorate="~{container}">
-<head>
-    <title th:text="#{notification.notifications}"></title>
-</head>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+    layout:decorate="~{container}">
+    <head>
+        <title th:text="#{notification.notifications}"></title>
+    </head>
 
-<body>
-
-<div layout:fragment="breadcrumbs">
-    <a th:href="@{/edition/enrolled}" th:text="#{general.home}"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{/notification/list}" th:text="#{notification.notifications}"></a>
-</div>
-
-<div layout:fragment="sidebar">
-    <a class="sidebar_item" th:href="@{/edition/enrolled}" th:text="#{general.home}"></a>
-    <a class="sidebar_item" th:href="@{/notification/list}" th:text="#{notification.notifications}"></a>
-    <form th:action="@{/notification/clear}" method="post">
-        <button class="warning sidebar_item" th:text="#{notification.clear_all}"></button>
-    </form>
-</div>
-
-<div layout:fragment="content">
-
-    <h1 class="title" th:text="#{notification.notifications}"></h1>
-
-    <table class="table">
-        <tr class="table_header">
-            <th th:text="#{notification.message}"></th>
-            <th></th>
-        </tr>
-        <tr th:each="notification : ${notifications}">
-            <td th:text="${notification.text}"></td>
-            <td><div class="table_actions">
-                <a th:href="@{|${notification.page}|}" class="text-button" th:text="#{general.view}"></a>
-            </div></td>
-        </tr>
-    </table>
-
-</div>
-
-</body>
-</html>
\ No newline at end of file
+    <body>
+        <main layout:fragment="content">
+            <div class="flex space-between align-center mb-5">
+                <h1 class="font-800" th:text="#{notification.notifications}"></h1>
+                <form th:action="@{/notification/clear}" method="post">
+                    <button
+                        class="button"
+                        data-style="outlined"
+                        th:text="#{notification.clear_all}"></button>
+                </form>
+            </div>
+            <div class="grid">
+                <table class="table" data-style="surface">
+                    <tr class="table__header">
+                        <th th:text="#{notification.message}"></th>
+                        <th></th>
+                    </tr>
+                    <tr th:if="${notifications.isEmpty()}">
+                        <td colspan="2">No notifications.</td>
+                    </tr>
+                    <tr th:each="notification : ${notifications}">
+                        <td th:text="${notification.text}"></td>
+                        <td>
+                            <div class="flex gap-3">
+                                <a
+                                    th:href="@{|${notification.page}|}"
+                                    class="text-button"
+                                    th:text="#{general.view}"></a>
+                            </div>
+                        </td>
+                    </tr>
+                </table>
+            </div>
+        </main>
+    </body>
+</html>
diff --git a/src/main/resources/templates/person/change_role.html b/src/main/resources/templates/person/change_role.html
index 9c258d49f994bbae611403e582889b3e76264331..b834e3a5d5ce1dbdfad50a9155d2f816c34dad0c 100644
--- a/src/main/resources/templates/person/change_role.html
+++ b/src/main/resources/templates/person/change_role.html
@@ -18,19 +18,33 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <dialog th:fragment="overlay" th:id="|role-${role.person.id}-overlay|" class="dialog">
+            <div class="flex vertical p-7">
+                <h2 class="font-500 underlined">Change role</h2>
 
-<div th:fragment="overlay" th:id="|role-${role.person.id}-overlay|" class="hidden confirm overlay">
-    <div class="boxed-content">
-        <p class="confirm_text" th:text="#{person.change_role.confirm(${role.person.username})}"></p>
-        <div class="form-buttons">
-            <button type=button class="warning text-button" th:text="#{general.cancel}"
-                    th:onclick="|toggleOverlay('role-${role.person.id}-overlay')|"></button>
-            <button type=button class="text-button" th:text="#{person.change_role}" th:onclick="|submitForm('change-role-${role.person.id}')|"></button>
-        </div>
-    </div>
-</div>
+                <p
+                    class="confirm_text"
+                    th:text="#{person.change_role.confirm(${role.person.displayName})}"></p>
 
-</body>
-</html>
\ No newline at end of file
+                <div class="flex space-between">
+                    <button
+                        type="button"
+                        class="button p-less"
+                        data-style="outlined"
+                        th:text="#{general.cancel}"
+                        data-cancel></button>
+                    <button
+                        type="button"
+                        class="button p-less"
+                        th:text="#{person.change_role}"
+                        th:onclick="|submitForm('change-role-${role.person.id}')|"></button>
+                </div>
+            </div>
+        </dialog>
+    </body>
+</html>
diff --git a/src/main/resources/templates/person/import.html b/src/main/resources/templates/person/import.html
index b032a35408801374e40862cc302d97046c40c348..2f6fbf1e7d81319f7e4dc5bd83bbc8f0c047954f 100644
--- a/src/main/resources/templates/person/import.html
+++ b/src/main/resources/templates/person/import.html
@@ -18,94 +18,142 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
-
-<div th:fragment="overlay(listPage)" th:if="${@authorizationService.canImportMembersToEdition(edition.id)}" id="import-overlay" class="hidden overlay">
-    <script>
-        function readColumnOptions() {
-            let file = $("#file")[0].files[0];
-            let reader = new FileReader();
-            reader.onload = () => {
-                let headers = reader.result.split("\n")[0].split(";");
-
-                let idColumn = document.getElementById("username-column");
-                let roleColumn = document.getElementById("role-column");
-
-                idColumn.disabled = false;
-                roleColumn.disabled = false;
-                idColumn.innerHTML = "";
-                roleColumn.innerHTML = "";
-
-                for (let i in headers) {
-                    let idOption = document.createElement("option");
-                    idOption.value = i.toString();
-                    idOption.text = headers[i];
-                    if (i === 0) idOption.selected = true;
-                    idColumn.appendChild(idOption);
-                    let roleOption = document.createElement("option");
-                    roleOption.value = i.toString();
-                    roleOption.text = headers[i];
-                    if (i === 1) roleOption.selected = true;
-                    roleColumn.appendChild(roleOption);
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <dialog
+            th:fragment="overlay(listPage)"
+            th:if="${@authorizationService.canImportMembersToEdition(edition.id)}"
+            id="import-overlay"
+            class="dialog">
+            <script>
+                function readColumnOptions() {
+                    let file = $("#file")[0].files[0];
+                    let reader = new FileReader();
+                    reader.onload = () => {
+                        let headers = reader.result.split("\n")[0].split(";");
+
+                        let idColumn = document.getElementById("username-column");
+                        let roleColumn = document.getElementById("role-column");
+
+                        idColumn.disabled = false;
+                        roleColumn.disabled = false;
+                        idColumn.innerHTML = "";
+                        roleColumn.innerHTML = "";
+
+                        for (let i in headers) {
+                            let idOption = document.createElement("option");
+                            idOption.value = i.toString();
+                            idOption.text = headers[i];
+                            if (i === 0) idOption.selected = true;
+                            idColumn.appendChild(idOption);
+                            let roleOption = document.createElement("option");
+                            roleOption.value = i.toString();
+                            roleOption.text = headers[i];
+                            if (i === 1) roleOption.selected = true;
+                            roleColumn.appendChild(roleOption);
+                        }
+                    };
+                    reader.readAsText(file);
+
+                    // let fileLabel = document.getElementById("file-label");
+                    // fileLabel.innerText = file.name;
                 }
-            };
-            reader.readAsText(file);
-
-            // let fileLabel = document.getElementById("file-label");
-            // fileLabel.innerText = file.name;
-        }
-    </script>
-    <form th:object="${participantImport}" th:action="@{|/edition/${edition.id}/import/participants/${listPage}|}" method="post" class="boxed-content"  enctype="multipart/form-data">
-        <h1 class="underlined title" th:text="#{person.import}"></h1>
-        <div class="form-group">
-
-            <label for="file" th:text="#{general.file}"></label>
-            <input type="file" id="file" name="file" accept="text/csv"
-                   onchange="readColumnOptions()" required/>
-
-            <div class="form-separator"></div>
-
-            <div>
-                <label for="username-column" th:text="#{person.import.username_column}"></label>
-                <div class="tooltip">
-                    <span class="tooltip_icon">?</span>
-                    <p class="tooltip_text" th:text="#{person.import.username_column.help}"></p>
+            </script>
+
+            <form
+                th:object="${participantImport}"
+                th:action="@{|/edition/${edition.id}/import/participants/${listPage}|}"
+                method="post"
+                class="flex vertical p-7"
+                enctype="multipart/form-data">
+                <h1 class="underlined font-500" th:text="#{person.import}"></h1>
+
+                <div class="grid col-2 align-center" style="--col-1: minmax(0, 10rem)">
+                    <label for="file" th:text="#{general.file}"></label>
+                    <input
+                        type="file"
+                        id="file"
+                        name="file"
+                        accept="text/csv"
+                        onchange="readColumnOptions()"
+                        required />
+
+                    <div class="underlined" style="grid-column: span 2"></div>
+
+                    <div>
+                        <label
+                            for="username-column"
+                            th:text="#{person.import.username_column}"></label>
+                        <div class="tooltip">
+                            <span class="tooltip__control fa-solid fa-question"></span>
+                            <div role="tooltip" style="white-space: initial; min-width: 16rem">
+                                <p th:text="#{person.import.username_column.help}"></p>
+                            </div>
+                        </div>
+                    </div>
+
+                    <select
+                        id="username-column"
+                        th:name="idColumn"
+                        class="textfield"
+                        disabled
+                        required></select>
+
+                    <div>
+                        <label for="role-column" th:text="#{person.import.role_column}"></label>
+                        <div class="tooltip">
+                            <span class="tooltip__control fa-solid fa-question"></span>
+                            <div role="tooltip" style="white-space: initial; min-width: 20rem">
+                                <p th:text="#{person.import.role_column.help}"></p>
+                            </div>
+                        </div>
+                    </div>
+                    <select
+                        id="role-column"
+                        th:name="roleColumn"
+                        class="textfield"
+                        disabled
+                        required></select>
+
+                    <div>
+                        <label for="headers" th:text="#{general.headers}"></label>
+                        <div class="tooltip">
+                            <span class="tooltip__control fa-solid fa-question"></span>
+                            <div
+                                role="tooltip"
+                                style="
+                                    white-space: initial;
+                                    min-width: 24rem;
+                                    transform-origin: bottom left;
+                                    top: initial;
+                                    bottom: 1.2rem;
+                                ">
+                                <p th:text="#{general.headers.help}"></p>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="checkbox">
+                        <input id="headers" th:name="hasHeaders" type="checkbox" />
+                        <label for="headers" th:text="#{general.headers.enter}"></label>
+                    </div>
                 </div>
-            </div>
-            <select id="username-column" th:name="idColumn" class="selectbox" disabled required>
-            </select>
-
-            <div>
-                <label for="role-column" th:text="#{person.import.role_column}"></label>
-                <div class="tooltip">
-                    <span class="tooltip_icon">?</span>
-                    <p class="tooltip_text" th:text="#{person.import.role_column.help}"></p>
-                </div>
-            </div>
-            <select id="role-column" th:name="roleColumn" class="selectbox" disabled required>
-            </select>
-
-            <div>
-                <label for="headers" th:text="#{general.headers}"></label>
-                <div class="tooltip">
-                    <span class="tooltip_icon">?</span>
-                    <p class="tooltip_text" th:text="#{general.headers.help}"></p>
+
+                <div class="flex space-between">
+                    <button
+                        type="button"
+                        class="button p-less"
+                        data-style="outlined"
+                        th:text="#{general.cancel}"
+                        data-cancel></button>
+                    <button
+                        type="submit"
+                        class="button p-less"
+                        th:text="#{general.import}"></button>
                 </div>
-            </div>
-            <div class="checkbox">
-                <input id="headers" th:name="hasHeaders" type="checkbox"/>
-                <label for="headers" th:text="#{general.headers.enter}"></label>
-            </div>
-
-        </div>
-        <div class="form-buttons">
-            <button type=button class="warning text-button" th:text="#{general.cancel}"
-                    onclick="toggleOverlay('import-overlay')"></button>
-            <button type=submit class="text-button" th:text="#{general.import}"></button>
-        </div>
-    </form>
-</div>
-
-</body>
-</html>
\ No newline at end of file
+            </form>
+        </dialog>
+    </body>
+</html>
diff --git a/src/main/resources/templates/person/make_ta.html b/src/main/resources/templates/person/make_ta.html
index 1d5201699ae8df302236e6d5bb821a2311170a8a..e20c5e7b6ab378a1b9b78896b2a2a7da444d5f68 100644
--- a/src/main/resources/templates/person/make_ta.html
+++ b/src/main/resources/templates/person/make_ta.html
@@ -18,19 +18,35 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
-
-<div th:fragment="overlay" th:if="${@authorizationService.canPromoteStudent(edition.id, role.person.id)}" th:id="|ta-${role.person.id}-overlay|" class="hidden confirm overlay">
-    <form th:action="@{|/role/${role.person.id}/${edition.id}/promote|}" method="post" class="boxed-content">
-        <p class="confirm_text" th:text="#{person.make_ta.confirm(${role.person.username})}"></p>
-        <div class="form-buttons">
-            <button type=button class="warning text-button" th:text="#{general.cancel}"
-                    th:onclick="|toggleOverlay('ta-${role.person.id}-overlay')|"></button>
-            <button type=submit class="text-button" th:text="#{person.make_ta}"></button>
-        </div>
-    </form>
-</div>
-
-</body>
-</html>
\ No newline at end of file
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <dialog
+            th:fragment="overlay"
+            th:if="${@authorizationService.canPromoteStudent(edition.id, role.person.id)}"
+            th:id="|ta-${role.person.id}-overlay|"
+            class="dialog">
+            <form
+                th:action="@{|/role/${role.person.id}/${edition.id}/promote|}"
+                method="post"
+                class="flex vertical p-7">
+                <h2 class="font-500 underlined">Make TA</h2>
+                <p th:text="#{person.make_ta.confirm(${role.person.username})}"></p>
+                <div class="flex space-between">
+                    <button
+                        type="button"
+                        class="button p-less"
+                        data-style="outlined"
+                        th:text="#{general.cancel}"
+                        data-cancel></button>
+                    <button
+                        type="submit"
+                        class="button p-less"
+                        th:text="#{person.make_ta}"></button>
+                </div>
+            </form>
+        </dialog>
+    </body>
+</html>
diff --git a/src/main/resources/templates/person/note.html b/src/main/resources/templates/person/note.html
index daa7963d6e6f19b814d56c4f934fe301447d06fb..bfa40d39ef3c1b0abbd4f6542e0162edb1fe3b6a 100644
--- a/src/main/resources/templates/person/note.html
+++ b/src/main/resources/templates/person/note.html
@@ -18,28 +18,52 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
-
-<div th:fragment="overlay(listPage)" th:if="${@authorizationService.canEditRoleNote(role.id)}" th:id="|note-${role.person.id}-overlay|" class="hidden overlay">
-    <form th:object="${note}" th:action="@{|/role/note/${listPage}|}" method="post" class="boxed-content">
-        <h1 class="underlined title" th:text="#{person.note}"></h1>
-        <div class="form-group">
-            <input type="hidden" th:name="entityId.personId" th:value="${role.person.id}"/>
-            <input type="hidden" th:name="entityId.editionId" th:value="${edition.id}"/>
-            <span th:text="#{general.for}"></span>
-            <span th:text="${role.person.username}"></span>
-            <label for="note" th:text="#{general.note}"></label>
-            <textarea id="note" th:name="note" th:placeholder="#{general.note.enter}" th:text="${role.note?.note}"
-                      rows="6" type="text" class="textarea" required></textarea>
-        </div>
-        <div class="form-buttons">
-            <button type=button class="warning text-button" th:text="#{general.cancel}"
-                    th:onclick="|toggleOverlay('note-${role.person.id}-overlay')|"></button>
-            <button type=submit class="text-button" th:text="#{general.save}"></button>
-        </div>
-    </form>
-</div>
-
-</body>
-</html>
\ No newline at end of file
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <dialog
+            th:fragment="overlay(listPage)"
+            th:if="${@authorizationService.canEditRoleNote(role.id)}"
+            th:id="|note-${role.person.id}-overlay|"
+            class="dialog">
+            <form
+                th:object="${note}"
+                th:action="@{|/role/note/${listPage}|}"
+                method="post"
+                class="flex vertical p-7">
+                <h2 class="underlined font-500" th:text="#{person.note}"></h2>
+
+                <div class="grid col-2" style="--col-1: minmax(0, 3rem)">
+                    <input type="hidden" th:name="entityId.personId" th:value="${role.person.id}" />
+                    <input type="hidden" th:name="entityId.editionId" th:value="${edition.id}" />
+
+                    <span th:text="#{general.for}"></span>
+                    <span th:text="${role.person.displayName}"></span>
+
+                    <label for="note" th:text="#{general.note}"></label>
+                    <textarea
+                        id="note"
+                        th:name="note"
+                        th:placeholder="#{general.note.enter}"
+                        th:text="${role.note?.note}"
+                        rows="6"
+                        type="text"
+                        class="textfield"
+                        required></textarea>
+                </div>
+
+                <div class="flex space-between">
+                    <button
+                        type="button"
+                        class="button p-less"
+                        th:text="#{general.cancel}"
+                        data-style="outlined"
+                        data-cancel></button>
+                    <button type="submit" class="button p-less" th:text="#{general.save}"></button>
+                </div>
+            </form>
+        </dialog>
+    </body>
+</html>
diff --git a/src/main/resources/templates/person/remove.html b/src/main/resources/templates/person/remove.html
index f37374a5a9b19e6fbdf7fbd75cb96e7a0703c8d4..d9220e29bce6855405f9ea0d56d9e2bde5167142 100644
--- a/src/main/resources/templates/person/remove.html
+++ b/src/main/resources/templates/person/remove.html
@@ -18,19 +18,40 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <dialog
+            th:fragment="overlay(listPage)"
+            th:if="${@authorizationService.canRemovePersonFromEdition(edition.id, role.person.id)}"
+            th:id="|remove-${role.person.id}-overlay|"
+            class="dialog">
+            <form
+                th:action="@{|/edition/${edition.id}/${listPage}/${role.person.id}/remove|}"
+                method="post"
+                class="flex vertical p-7">
+                <h2 class="font-500 underlined" th:text="#{group.remove_member}"></h2>
 
-<div th:fragment="overlay(listPage)" th:if="${@authorizationService.canRemovePersonFromEdition(edition.id, role.person.id)}" th:id="|remove-${role.person.id}-overlay|" class="hidden confirm overlay">
-    <form th:action="@{|/edition/${edition.id}/${listPage}/${role.person.id}/remove|}" method="post" class="boxed-content">
-        <p class="confirm_text" th:text="#{person.remove.confirm(${role.person.username})}"></p>
-        <div class="form-buttons">
-            <button type=button class="warning text-button" th:text="#{general.cancel}"
-                    th:onclick="|toggleOverlay('remove-${role.person.id}-overlay')|"></button>
-            <button type=submit class="text-button" th:text="#{general.remove}"></button>
-        </div>
-    </form>
-</div>
+                <p
+                    class="confirm_text"
+                    th:text="#{person.remove.confirm(${role.person.displayName})}"></p>
 
-</body>
-</html>
\ No newline at end of file
+                <div class="flex space-between">
+                    <button
+                        type="button"
+                        class="button p-less"
+                        data-style="outlined"
+                        th:text="#{general.cancel}"
+                        data-cancel></button>
+                    <button
+                        type="submit"
+                        class="button p-less"
+                        data-type="error"
+                        th:text="#{general.remove}"></button>
+                </div>
+            </form>
+        </dialog>
+    </body>
+</html>
diff --git a/src/main/resources/templates/person/view.html b/src/main/resources/templates/person/view.html
index abaf1613910c05ae8f521151366d1e8683de62b0..c45591be5f35d4ff6cd91316263f14a88a9bfe49 100644
--- a/src/main/resources/templates/person/view.html
+++ b/src/main/resources/templates/person/view.html
@@ -18,40 +18,35 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
-      layout:decorate="~{container}">
-<head>
-    <title th:text="${person.username}"></title>
-</head>
-
-<body>
-
-<div layout:fragment="breadcrumbs" class="hidden">x</div>
-
-<div layout:fragment="sidebar" class="hidden"></div>
-
-<div layout:fragment="content">
-
-    <h1 class="title" th:text="${person.displayName}"></h1>
-
-    <div class="info_row">
-        <div class="card_stats card">
-            <div class="card_stats_row">
-                <span th:text="#{person.student_number}"></span>
-                <span th:text="${person.number}"></span>
-            </div>
-            <div class="card_stats_row">
-                <span th:text="#{person.username}"></span>
-                <span th:text="${person.username}"></span>
-            </div>
-            <div class="card_stats_row">
-                <span th:text="#{person.email}"></span>
-                <span th:text="${person.email}"></span>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+    layout:decorate="~{container}">
+    <head>
+        <title th:text="${person.username}"></title>
+    </head>
+
+    <body>
+        <main layout:fragment="content">
+            <h1 class="font-800 mb-5" th:text="${person.displayName}"></h1>
+
+            <div class="surface">
+                <div class="flex vertical gap-3">
+                    <div class="underlined flex space-between">
+                        <span th:text="#{person.student_number}"></span>
+                        <span th:text="${person.number}"></span>
+                    </div>
+                    <div class="underlined flex space-between">
+                        <span th:text="#{person.username}"></span>
+                        <span th:text="${person.username}"></span>
+                    </div>
+                    <div class="underlined flex space-between">
+                        <span th:text="#{person.email}"></span>
+                        <span th:text="${person.email}"></span>
+                    </div>
+                </div>
             </div>
-        </div>
-    </div>
-
-</div>
-
-</body>
-</html>
\ No newline at end of file
+        </main>
+    </body>
+</html>
diff --git a/src/main/resources/templates/privacy_statement.html b/src/main/resources/templates/privacy_statement.html
index 0ec7f25d5e3f3e3af0156e17dbf782dae8d5a0cb..2792e5d911da2488e3295a9ab71fe72690adf9e7 100644
--- a/src/main/resources/templates/privacy_statement.html
+++ b/src/main/resources/templates/privacy_statement.html
@@ -18,145 +18,137 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
-      layout:decorate="~{container}">
-<head>
-    <link rel="stylesheet" type="text/css" th:href="@{/css/article.css}"/>
-
-    <title th:text="#{general.app_name}"></title>
-</head>
-
-<body>
-
-<div layout:fragment="sidebar" class="hidden"></div>
-<div layout:fragment="breadcrumbs" class="hidden"></div>
-
-<div layout:fragment="content">
-
-    <h1 class="title">Privacy statement</h1>
-
-    <p>
-        <b>This privacy statement is a first version. Some aspects in this privacy statement might be changed in the
-            future to be in line with the law and best practices</b>
-    </p>
-    <p>
-        The Submit project is a project from the TU Delft. In this privacy statement, it will be explained what
-        personal data Submit collects when using the website. In this document the term Labrador will also be
-        mentioned. Labrador is a project of the TU Delft, which will include Submit.
-    </p>
-
-    <h2>Topics</h2>
-    <ul>
-        <li>What data do we collect?</li>
-        <li>How do we collect your data?</li>
-        <li>How will we use your data?</li>
-        <li>How will we store your data?</li>
-        <li>Other matters</li>
-    </ul>
-
-    <h2>What data do we collect</h2>
-    <p>
-        To support your education at the TU Delft, the university has a legal ground to store your data. To be more
-        specific, it is a contractual necessity: the data processing is necessary for the performance of the
-        contract between the student and TU Delft. Because of this, Submit collects the following data:
-    <ul>
-        <li>Name</li>
-        <li>Email</li>
-        <li>Student number</li>
-        <li>NetID</li>
-        <li>Affiliation (if the user is a student or teacher)</li>
-        <li>Which department/faculty the user is part of</li>
-    </ul>
-    </p>
-    <p>
-        Furthermore, there exist multiple subgroups in the system. We will describe for each subgroup the extra data
-        we store.
-    </p>
-
-    <h2>Student</h2>
-    <p>
-        We collect the following information about students:
-    </p>
-    <ul>
-        <li>The mentor group they are enrolled in</li>
-        <li>Courses they are enrolled in</li>
-        <li>The groups the student is/was part of</li>
-        <li>Assignments the student submitted</li>
-        <li>Grades and feedback the student received</li>
-    </ul>
-
-    <h2>TA</h2>
-    <p>
-        For a TA (teaching assistant), we collect the following data:
-    </p>
-    <ul>
-        <li>Notes left by the TA on assignments, submissions, groups and users</li>
-    </ul>
-
-    <h2>Teacher</h2>
-    <p>
-        For a teacher, we collect the following data:
-    </p>
-    <ul>
-        <li>All courses the teacher teaches</li>
-        <li>Notes left by the teacher on assignments, submissions, groups and users</li>
-    </ul>
-
-    <h2>How do we collect your data</h2>
-    <p>
-        All data we collect is collected when using the Submit website.
-    </p>
-
-    <h2>How will we use your data</h2>
-    <p>
-        At this moment, we only use the data given to support the functionality Submit promises, giving students an
-        easy way to submit and sign off work and giving teaching staff an easy to use system to support signing off
-        and providing assignments.
-    </p>
-
-    <p>
-        Data is being used on a need to know basis. This means that student-assistants only see, what they need to
-        see to fulfil their duties as teaching assistant and the same for teachers. Only administrators have access
-        to the database.
-    </p>
-
-    <p>
-        A teacher might export data to use for purposes to calculate final grades or import it into other tools,
-        namely BrightSpace and Osiris.
-    </p>
-
-    <h2>How do we store your data</h2>
-    <p>
-        To comply with GDPR regulations, all data is stored on site at the TU Delft. The data stored on our servers
-        will be deleted after 5 years.
-    </p>
-
-    <p>
-        Access to data is strictly regulated with only select admins having access to the database. All other
-        entities need to use the website itself, where, as mentioned previously, information is provided on a need
-        to know basis.
-    </p>
-
-    <h2>Other matters</h2>
-    <p>
-        For matters like:
-    </p>
-
-    <ul>
-        <li>What are your data protection rights</li>
-        <li>Which cookies we set and why</li>
-    </ul>
-
-    <p>
-        Please refer to the <a href="https://www.tudelft.nl/privacy-statement/">Privacy Statement of the TU Delft</a>
-    </p>
-
-    <p>
-        If you have any questions about the privacy policy or want to make use of your rights, please contact the
-        <a href="mailto:privacy-tud@tudelft.nl">following email address</a>
-    </p>
-
-</div>
-
-</body>
-</html>
\ No newline at end of file
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+    layout:decorate="~{container}">
+    <head>
+        <title th:text="#{general.app_name}"></title>
+    </head>
+
+    <body>
+        <main layout:fragment="content">
+            <h1 class="font-800 mb-5">Privacy statement</h1>
+
+            <p class="fw-500 mb-3">
+                This privacy statement is a first version. Some aspects in this privacy statement
+                might be changed in the future to be in line with the law and best practices
+            </p>
+            <p class="mb-5">
+                The Submit project is a project from the TU Delft. In this privacy statement, it
+                will be explained what personal data Submit collects when using the website. In this
+                document the term Labrador will also be mentioned. Labrador is a project of the TU
+                Delft, which will include Submit.
+            </p>
+
+            <h2 class="font-500 mb-1">Topics</h2>
+            <ul class="list mb-5">
+                <li>What data do we collect?</li>
+                <li>How do we collect your data?</li>
+                <li>How will we use your data?</li>
+                <li>How will we store your data?</li>
+                <li>Other matters</li>
+            </ul>
+
+            <h2 class="font-500 mb-1">What data do we collect</h2>
+            <p class="mb-1">
+                To support your education at the TU Delft, the university has a legal ground to
+                store your data. To be more specific, it is a contractual necessity: the data
+                processing is necessary for the performance of the contract between the student and
+                TU Delft. Because of this, Submit collects the following data:
+            </p>
+            <ul class="list mb-3">
+                <li>Name</li>
+                <li>Email</li>
+                <li>Student number</li>
+                <li>NetID</li>
+                <li>Affiliation (if the user is a student or teacher)</li>
+                <li>Which department/faculty the user is part of</li>
+            </ul>
+            <p class="mb-5">
+                Furthermore, there exist multiple subgroups in the system. We will describe for each
+                subgroup the extra data we store.
+            </p>
+
+            <h2 class="font-500 mb-1">Student</h2>
+            <p class="mb-1">We collect the following information about students:</p>
+            <ul class="list mb-5">
+                <li>The mentor group they are enrolled in</li>
+                <li>Courses they are enrolled in</li>
+                <li>The groups the student is/was part of</li>
+                <li>Assignments the student submitted</li>
+                <li>Grades and feedback the student received</li>
+            </ul>
+
+            <h2 class="font-500 mb-1">TA</h2>
+            <p class="mb-1">For a TA (teaching assistant), we collect the following data:</p>
+            <ul class="list mb-5">
+                <li>Notes left by the TA on assignments, submissions, groups and users</li>
+            </ul>
+
+            <h2 class="font-500 mb-1">Teacher</h2>
+            <p class="mb-1">For a teacher, we collect the following data:</p>
+            <ul class="list mb-5">
+                <li>All courses the teacher teaches</li>
+                <li>Notes left by the teacher on assignments, submissions, groups and users</li>
+            </ul>
+
+            <h2 class="font-500 mb-1">How do we collect your data</h2>
+            <p class="mb-5">All data we collect is collected when using the Submit website.</p>
+
+            <h2 class="font-500 mb-1">How will we use your data</h2>
+            <p class="mb-3">
+                At this moment, we only use the data given to support the functionality Submit
+                promises, giving students an easy way to submit and sign off work and giving
+                teaching staff an easy to use system to support signing off and providing
+                assignments.
+            </p>
+
+            <p class="mb-3">
+                Data is being used on a need to know basis. This means that student-assistants only
+                see, what they need to see to fulfil their duties as teaching assistant and the same
+                for teachers. Only administrators have access to the database.
+            </p>
+
+            <p class="mb-5">
+                A teacher might export data to use for purposes to calculate final grades or import
+                it into other tools, namely BrightSpace and Osiris.
+            </p>
+
+            <h2 class="font-500 mb-1">How do we store your data</h2>
+            <p class="mb-3">
+                To comply with GDPR regulations, all data is stored on site at the TU Delft. The
+                data stored on our servers will be deleted after 5 years.
+            </p>
+
+            <p class="mb-5">
+                Access to data is strictly regulated with only select admins having access to the
+                database. All other entities need to use the website itself, where, as mentioned
+                previously, information is provided on a need to know basis.
+            </p>
+
+            <h2 class="font-500 mb-1">Other matters</h2>
+            <p class="mb-1">For matters like:</p>
+
+            <ul class="list mb-1">
+                <li>What are your data protection rights</li>
+                <li>Which cookies we set and why</li>
+            </ul>
+
+            <p class="mb-3">
+                Please refer to the
+                <a class="link" href="https://www.tudelft.nl/privacy-statement/">
+                    Privacy Statement of the TU Delft
+                </a>
+            </p>
+
+            <p>
+                If you have any questions about the privacy policy or want to make use of your
+                rights, please contact the
+                <a class="link" href="mailto:privacy-tud@tudelft.nl">following email address</a>
+                .
+            </p>
+        </main>
+    </body>
+</html>
diff --git a/src/main/resources/templates/settings.html b/src/main/resources/templates/settings.html
index 6a604636422299de016550158a0cc4b09b623000..aa88a15193ec79862fc1c735b93c7aa306f4cc1b 100644
--- a/src/main/resources/templates/settings.html
+++ b/src/main/resources/templates/settings.html
@@ -18,39 +18,71 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
-
-<div th:fragment="overlay" th:if="${@authorizationService.isAuthenticated()}" id="settings-overlay" class="hidden overlay">
-    <form th:action="@{/settings}" th:method="patch" class="boxed-content">
-        <h1 class="underlined title" th:text="#{settings.settings}"></h1>
-
-        <div class="form-group" th:with="current = ${@userSettingsService.getOrCreateUserSettings()}">
-            <label for="on-added" th:text="#{settings.on_added}"></label>
-            <select id="on-added" class="selectbox" th:name="onAddedToCourse">
-                <option th:each="setting : ${T(nl.tudelft.submit.enums.NotificationPreference).values()}"
-                        th:value="${setting}"
-                        th:selected="${current.onAddedToCourse.name() == setting.name()}"
-                        th:text="#{|settings.notification.${#strings.toLowerCase(setting.name())}|}"></option>
-            </select>
-
-            <label for="on-grade" th:text="#{settings.on_grade}"></label>
-            <select id="on-grade" class="selectbox" th:name="onGradeUpdated">
-                <option th:each="setting : ${T(nl.tudelft.submit.enums.NotificationPreference).values()}"
-                        th:value="${setting}"
-                        th:selected="${current.onGradeUpdated.name() == setting.name()}"
-                        th:text="#{|settings.notification.${#strings.toLowerCase(setting.name())}|}"></option>
-            </select>
-
-            <input type="hidden" name="fromUrl" th:value="${#httpServletRequest.requestURI}"/>
-        </div>
-
-        <div class="form-buttons">
-            <button type=button class="warning text-button" th:text="#{general.cancel}" onclick="toggleOverlay('settings-overlay')"></button>
-            <button type=submit class="text-button" th:text="#{general.save}"></button>
-        </div>
-    </form>
-</div>
-
-</body>
-</html>
\ No newline at end of file
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <dialog
+            th:fragment="overlay"
+            th:if="${@authorizationService.isAuthenticated()}"
+            id="settings-overlay"
+            class="dialog">
+            <form th:action="@{/settings}" th:method="patch" class="flex vertical p-7">
+                <h1 class="underlined font-500" th:text="#{settings.settings}"></h1>
+
+                <div
+                    class="grid col-2 gap-3 align-center"
+                    style="--col-1: minmax(0, 14rem)"
+                    th:with="current = ${@userSettingsService.getOrCreateUserSettings()}">
+                    <label for="on-added" th:text="#{settings.on_added}"></label>
+                    <select id="on-added" class="textfield" th:name="onAddedToCourse">
+                        <option
+                            th:each="setting : ${T(nl.tudelft.submit.enums.NotificationPreference).values()}"
+                            th:value="${setting}"
+                            th:selected="${current.onAddedToCourse.name() == setting.name()}"
+                            th:text="#{|settings.notification.${#strings.toLowerCase(setting.name())}|}"></option>
+                    </select>
+
+                    <label for="on-grade" th:text="#{settings.on_grade}"></label>
+                    <select id="on-grade" class="textfield" th:name="onGradeUpdated">
+                        <option
+                            th:each="setting : ${T(nl.tudelft.submit.enums.NotificationPreference).values()}"
+                            th:value="${setting}"
+                            th:selected="${current.onGradeUpdated.name() == setting.name()}"
+                            th:text="#{|settings.notification.${#strings.toLowerCase(setting.name())}|}"></option>
+                    </select>
+
+                    <div class="underlined" style="grid-column: span 2"></div>
+
+                    <label for="theme">Theme (beta)</label>
+                    <select id="theme" class="textfield" onchange="setTheme(this.value)">
+                        <option value="tu">TU Delft</option>
+                        <option value="light">☀️ Light</option>
+                        <option value="dark">🌙 Dark</option>
+                    </select>
+                    <script>
+                        document.addEventListener("DOMContentLoaded", function () {
+                            document.getElementById("theme").value = localStorage.getItem("theme");
+                        });
+                    </script>
+
+                    <input
+                        type="hidden"
+                        name="fromUrl"
+                        th:value="${#httpServletRequest.requestURI}" />
+                </div>
+
+                <div class="flex space-between">
+                    <button
+                        type="button"
+                        class="button p-less"
+                        data-style="outlined"
+                        data-cancel
+                        th:text="#{general.cancel}"></button>
+                    <button type="submit" class="button p-less" th:text="#{general.save}"></button>
+                </div>
+            </form>
+        </dialog>
+    </body>
+</html>
diff --git a/src/main/resources/templates/statistics/view.html b/src/main/resources/templates/statistics/view.html
index 7f7bde655939395005b670eff16afc0ad6d6d8f8..d033df9dc9b95a2f8b53dfeb0aa45c89835c1e31 100644
--- a/src/main/resources/templates/statistics/view.html
+++ b/src/main/resources/templates/statistics/view.html
@@ -18,118 +18,128 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
-
-<div th:fragment="pass-fail" th:if="${statistics.passFailDistribution != null}" class="half card">
-    <div class="card_vertical-content card_top">
-        <h2 class="subtitle" th:text="#{statistics.pass_fail}"></h2>
-        <canvas id="pass-fail-chart" width="1000px" height="500px"></canvas>
-        <script th:inline="javascript">
-            const passColour = "rgba(0, 175, 200, 1)";
-            const failColour = "rgba(255, 100, 125, 1)";
-            const notStartedColour = "rgba(150, 150, 150, 1)";
-            var ctx = document.getElementById("pass-fail-chart").getContext("2d");
-            var passFailChart = new Chart(ctx, {
-                type: "pie",
-                data: {
-                    labels: [ [[#{grading.passed}]], [[#{grading.failed}]], [[#{grading.no_grade}]] ],
-                    datasets: [{
-                        data: [[${statistics.passFailDistribution}]],
-                        backgroundColor: [passColour, failColour, notStartedColour],
-                    }]
-                },
-                options: {
-                    legend: {
-                        position: "left",
-                        align: "start"
-                    }
-                }
-            });
-        </script>
-    </div>
-</div>
-
-<div th:fragment="pass-fail-script" th:if="${statistics.passFailDistribution != null}" class="half card">
-    <div class="card_vertical-content card_top">
-        <h2 class="subtitle" th:text="#{statistics.pass_fail}"></h2>
-        <canvas id="pass-fail-chart" width="1000px" height="500px"></canvas>
-        <script th:inline="javascript">
-            const passColour = "rgba(0, 175, 200, 1)";
-            const scriptPassColour = "rgba(0, 210, 225, 1)";
-            const failColour = "rgba(255, 100, 125, 1)";
-            const scriptFailColour = "rgba(255, 175, 185, 1)";
-            const notStartedColour = "rgba(150, 150, 150, 1)";
-            var ctx = document.getElementById("pass-fail-chart").getContext("2d");
-            var passFailChart = new Chart(ctx, {
-                type: "pie",
-                data: {
-                    labels: [ [[#{grading.passed}]], [[#{grading.script_passed}]], [[#{grading.failed}]], [[#{grading.script_failed}]], [[#{grading.no_grade}]] ],
-                    datasets: [{
-                        data: [[${statistics.passFailDistribution}]],
-                        backgroundColor: [passColour, scriptPassColour, failColour, scriptFailColour, notStartedColour],
-                    }]
-                },
-                options: {
-                    legend: {
-                        position: "left",
-                        align: "start"
-                    }
-                }
-            });
-        </script>
-    </div>
-</div>
-
-<div th:fragment="score-distribution" th:if="${statistics.scoreDistribution != null}" class="half card">
-    <div class="card_vertical-content card_top">
-        <h2 class="subtitle" th:text="#{statistics.score_distribution}"></h2>
-        <canvas id="grade-distribution-chart" width="1000px" height="500px"></canvas>
-        <script th:inline="javascript">
-            /*<![CDATA[*/
-            const colour = "rgba(255, 100, 125, 1)";
-            var ctx = document.getElementById("grade-distribution-chart").getContext("2d");
-            var dataValues = [[${statistics.scoreDistribution}]];
-            var dataLabels = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
-            var scoreDistribution = new Chart(ctx, {
-                type: "bar",
-                data: {
-                    labels: dataLabels,
-                    datasets: [{
-                        label: "#Students",
-                        data: dataValues,
-                        backgroundColor: colour,
-                        borderWidth: 1
-                    }]
-                },
-                options: {
-                    scales: {
-                        xAxes: [{
-                            display: false,
-                            barPercentage: 1.25,
-                            ticks: {
-                                max: 9,
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <div
+            th:fragment="pass-fail"
+            th:if="${statistics.passFailDistribution != null}"
+            class="surface">
+            <div class="flex vertical">
+                <h2 class="subtitle" th:text="#{statistics.pass_fail}"></h2>
+                <canvas id="pass-fail-chart" width="1000px" height="500px"></canvas>
+                <script th:inline="javascript">
+                    const passColour = "rgba(0, 175, 200, 1)";
+                    const failColour = "rgba(255, 100, 125, 1)";
+                    const notStartedColour = "rgba(150, 150, 150, 1)";
+                    var ctx = document.getElementById("pass-fail-chart").getContext("2d");
+                    var passFailChart = new Chart(ctx, {
+                        type: "pie",
+                        data: {
+                            labels: [ [[#{grading.passed}]], [[#{grading.failed}]], [[#{grading.no_grade}]] ],
+                            datasets: [{
+                                data: [[${statistics.passFailDistribution}]],
+                                backgroundColor: [passColour, failColour, notStartedColour],
+                            }]
+                        },
+                        options: {
+                            legend: {
+                                position: "left",
+                                align: "start"
                             }
-                        }, {
-                            display: true,
-                            ticks: {
-                                autoSkip: false,
-                                max: 10,
-                            }
-                        }],
-                        yAxes: [{
-                            ticks: {
-                                stepSize: 1,
-                                beginAtZero: true
+                        }
+                    });
+                </script>
+            </div>
+        </div>
+
+        <div
+            th:fragment="pass-fail-script"
+            th:if="${statistics.passFailDistribution != null}"
+            class="surface">
+            <div class="flex vertical">
+                <h2 class="font-500" th:text="#{statistics.pass_fail}"></h2>
+                <canvas id="pass-fail-chart" width="1000px" height="500px"></canvas>
+                <script th:inline="javascript">
+                    const passColour = "rgba(0, 175, 200, 1)";
+                    const scriptPassColour = "rgba(0, 210, 225, 1)";
+                    const failColour = "rgba(255, 100, 125, 1)";
+                    const scriptFailColour = "rgba(255, 175, 185, 1)";
+                    const notStartedColour = "rgba(150, 150, 150, 1)";
+                    var ctx = document.getElementById("pass-fail-chart").getContext("2d");
+                    var passFailChart = new Chart(ctx, {
+                        type: "pie",
+                        data: {
+                            labels: [ [[#{grading.passed}]], [[#{grading.script_passed}]], [[#{grading.failed}]], [[#{grading.script_failed}]], [[#{grading.no_grade}]] ],
+                            datasets: [{
+                                data: [[${statistics.passFailDistribution}]],
+                                backgroundColor: [passColour, scriptPassColour, failColour, scriptFailColour, notStartedColour],
+                            }]
+                        },
+                        options: {
+                            legend: {
+                                position: "left",
+                                align: "start"
                             }
-                        }]
-                    }
-                }
-            });
-            /*]]>*/
-        </script>
-    </div>
-</div>
+                        }
+                    });
+                </script>
+            </div>
+        </div>
 
-</body>
-</html>
\ No newline at end of file
+        <div
+            th:fragment="score-distribution"
+            th:if="${statistics.scoreDistribution != null}"
+            class="surface">
+            <div class="flex vertical">
+                <h2 class="font-500" th:text="#{statistics.score_distribution}"></h2>
+                <canvas id="grade-distribution-chart" width="1000px" height="500px"></canvas>
+                <script th:inline="javascript">
+                    /*<![CDATA[*/
+                    const colour = "rgba(255, 100, 125, 1)";
+                    var ctx = document.getElementById("grade-distribution-chart").getContext("2d");
+                    var dataValues = [[${statistics.scoreDistribution}]];
+                    var dataLabels = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
+                    var scoreDistribution = new Chart(ctx, {
+                        type: "bar",
+                        data: {
+                            labels: dataLabels,
+                            datasets: [{
+                                label: "#Students",
+                                data: dataValues,
+                                backgroundColor: colour,
+                                borderWidth: 1
+                            }]
+                        },
+                        options: {
+                            scales: {
+                                xAxes: [{
+                                    display: false,
+                                    barPercentage: 1.25,
+                                    ticks: {
+                                        max: 9,
+                                    }
+                                }, {
+                                    display: true,
+                                    ticks: {
+                                        autoSkip: false,
+                                        max: 10,
+                                    }
+                                }],
+                                yAxes: [{
+                                    ticks: {
+                                        stepSize: 1,
+                                        beginAtZero: true
+                                    }
+                                }]
+                            }
+                        }
+                    });
+                    /*]]>*/
+                </script>
+            </div>
+        </div>
+    </body>
+</html>
diff --git a/src/main/resources/templates/submission/feedback.html b/src/main/resources/templates/submission/feedback.html
index d3d1ed38da9155938aceefcfaf60a8de9620667c..e725b97960e945e5b5ec83b75bc59502c06884cf 100644
--- a/src/main/resources/templates/submission/feedback.html
+++ b/src/main/resources/templates/submission/feedback.html
@@ -18,151 +18,220 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
-
-<div th:fragment="overlay(page)" th:id="|feedback-${submission.id}-overlay|" class="hidden overlay">
-    <form th:object="${feedbackCreate}" th:action="${page == 'group'} ? @{|/grade/group/${group.id}/${assignment.id}|} : @{|/grade/assignment/${assignment.id}|}" method="post" class="boxed-content">
-        <h1 class="underlined title" th:text="#{submission.feedback}"></h1>
-
-        <div th:if="${@authorizationService.canGradeSubmission(submission.id)}" th:remove="tag">
-            <p th:unless="${submission.isLatest}" style="margin-top: 2rem;" th:text="#{submission.grade_latest_only}"></p>
-            <div th:unless="${submission.isLatest}" class="form-separator"></div>
-
-            <div class="form-group" th:if="${submission.isLatest}">
-                <input type="hidden" th:name="submissionId" th:value="${submission.id}"/>
-
-                <label for="feedback" th:text="#{submission.feedback}"></label>
-                <textarea id="feedback" th:name="textualFeedback" th:placeholder="#{submission.feedback.enter}"
-                          rows="6" type="text" class="textarea"></textarea>
-
-                <label for="grade" th:text="#{grading.grade}"></label>
-
-                <select th:if="${assignment.scoreType.name() == 'PASS_FAIL'}" id="grade" th:name="grade" class="selectbox">
-                    <option value=0 th:text="#{grading.failed}"></option>
-                    <option value=1 th:text="#{grading.passed}"></option>
-                </select>
-
-                <input th:if="${assignment.scoreType.name() == 'DUTCH_GRADE' or assignment.scoreType.name() == 'DUTCH_GRADE_1000'}"
-                       id="grade" th:name="grade" class="textfield" type="number" min=1 max=10
-                       th:step="${assignment.scoreType.name() == 'DUTCH_GRADE'} ? 0.1 : 0.01"
-                       th:placeholder="#{grading.grade.enter}"/>
-
-                <input th:if="${assignment.scoreType.name() != 'PASS_FAIL' and assignment.scoreType.name() != 'DUTCH_GRADE' and assignment.scoreType.name() != 'DUTCH_GRADE_1000'}"
-                       id="grade" th:name="grade" class="textfield" type="number" min="0"
-                       th:max="${@gradeService.getMaxScore(assignment.scoreType)}" step=1 th:placeholder="#{grading.grade.enter}"/>
-            </div>
-        </div>
-
-        <div class="tabs">
-            <button class="active tab" type="button" th:data-tab="|staff-feedback-${submission.id}|" th:text="#{submission.staff_feedback}"></button>
-            <button class="tab" type="button" th:data-tab="|script-feedback-${submission.id}|" th:text="#{submission.script_feedback}"></button>
-        </div>
-
-        <div th:id="|staff-feedback-${submission.id}|" class="list">
-            <div th:if="${submission.feedback.isEmpty()}" class="list_item">
-                <div class="list_item_content fs_400">
-                    <span th:text="#{submission.no_feedback}"></span>
-                </div>
-            </div>
-            <th:block th:each="feedback : ${submission.feedback}" th:if="${@authorizationService.canViewGroupFeedback(submission.group.id, feedback)}">
-                <div th:data-item="${feedback.id}" class="collapsed list_item">
-                    <div class="list_item_content fs_400">
-                        <span th:if="${feedback.isScriptFeedback}" th:text="#{submission.feedback_from(${feedback.authorId == -1 ? 'script' : @scriptService.getScriptById(feedback.authorId).name})}"></span>
-                        <span th:unless="${feedback.isScriptFeedback}" th:text="#{submission.feedback_by(${@personCacheManager.getOrThrow(feedback.authorId)?.displayName})}"></span>
-                        <span class="tag" style="float: none;" th:if="${feedback.gradeId}" th:text="${@gradeService.getScoreDisplayString(grades[feedback.gradeId])}"></span>
-                    </div>
-                    <div class="list_actions">
-                        <button class="text-button fas fa-chevron-down" type="button" th:onclick="|toggleCollapse('staff-feedback-${submission.id}', '${feedback.id}')|"></button>
-                    </div>
-                </div>
-                <div class="list_subitems">
-                    <div class="list_subitem">
-                        <div class="list_subitem_content fs_300">
-                            <p style="max-width: 80ch;" th:each="text : ${#strings.listSplit(feedback.textualFeedback, '&#10;')}" th:text="${text}"></p>
-                        </div>
+<html lang="en" xmlns:th="http://www.thymeleaf.org">
+    <body>
+        <dialog
+            th:fragment="overlay(page)"
+            th:id="|feedback-${submission.id}-overlay|"
+            th:data-closable="${@authorizationService.canGradeSubmission(submission.id) ? null : 'true'}"
+            class="dialog">
+            <form
+                th:object="${feedbackCreate}"
+                th:action="@{__${page == 'group' ? ('/grade/group/' + group.id + '/' + assignment.id) :
+                             page == 'assist' ? ('/grade/edition/' + edition.id + '/' + assignment.id) :
+                             ('/grade/assignment/' + assignment.id)}__}"
+                method="post"
+                class="flex vertical p-7">
+                <h1 class="underlined font-500" th:text="#{submission.feedback}"></h1>
+
+                <th:block th:if="${@authorizationService.canGradeSubmission(submission.id)}">
+                    <p
+                        th:unless="${submission.isLatest}"
+                        th:text="#{submission.grade_latest_only}"></p>
+                    <div th:unless="${submission.isLatest}" class="form-separator"></div>
+
+                    <div
+                        class="grid col-2 gap-3"
+                        style="--col-1: minmax(0, 8rem)"
+                        th:if="${submission.isLatest}">
+                        <input type="hidden" th:name="submissionId" th:value="${submission.id}" />
+
+                        <label for="feedback" th:text="#{submission.feedback}"></label>
+                        <textarea
+                            id="feedback"
+                            th:name="textualFeedback"
+                            th:placeholder="#{submission.feedback.enter}"
+                            rows="6"
+                            class="textfield"></textarea>
+
+                        <label for="grade" th:text="#{grading.grade}"></label>
+
+                        <select
+                            th:if="${assignment.scoreType.name() == 'PASS_FAIL'}"
+                            id="grade"
+                            th:name="grade"
+                            class="textfield">
+                            <option value="0" th:text="#{grading.failed}"></option>
+                            <option value="1" th:text="#{grading.passed}"></option>
+                        </select>
+
+                        <input
+                            th:if="${assignment.scoreType.name() == 'DUTCH_GRADE' or assignment.scoreType.name() == 'DUTCH_GRADE_1000'}"
+                            id="grade"
+                            th:name="grade"
+                            class="textfield"
+                            type="number"
+                            min="1"
+                            max="10"
+                            th:step="${assignment.scoreType.name() == 'DUTCH_GRADE'} ? 0.1 : 0.01"
+                            th:placeholder="#{grading.grade.enter}" />
+
+                        <input
+                            th:if="${assignment.scoreType.name() != 'PASS_FAIL' and assignment.scoreType.name() != 'DUTCH_GRADE' and assignment.scoreType.name() != 'DUTCH_GRADE_1000'}"
+                            id="grade"
+                            th:name="grade"
+                            class="textfield"
+                            type="number"
+                            min="0"
+                            th:max="${@gradeService.getMaxScore(assignment.scoreType)}"
+                            step="1"
+                            th:placeholder="#{grading.grade.enter}" />
                     </div>
+                </th:block>
+
+                <div class="tabs" role="tablist" th:unless="${submission.scriptResults.isEmpty()}">
+                    <button
+                        role="tab"
+                        th:id="|staff-feedback-${submission.id}-tab|"
+                        type="button"
+                        aria-selected="true"
+                        th:aria-controls="|staff-feedback-${submission.id}|"
+                        th:text="#{submission.staff_feedback}"></button>
+                    <button
+                        role="tab"
+                        th:id="|script-feedback-${submission.id}-tab|"
+                        type="button"
+                        aria-selected="false"
+                        th:aria-controls="|script-feedback-${submission.id}|"
+                        th:text="#{submission.script_feedback}"></button>
                 </div>
-            </th:block>
-        </div>
 
-        <div th:id="|script-feedback-${submission.id}|" class="hidden" th:if="${submission.scriptResults.isEmpty()}">
-            <div th:id="|staff-feedback-${submission.id}|" class="list">
-                <div th:if="${submission.scriptResults.isEmpty()}" class="list_item">
-                    <div class="list_item_content fs_400">
+                <div th:id="|staff-feedback-${submission.id}|" class="accordion">
+                    <div th:if="${submission.feedback.isEmpty()}">
                         <span th:text="#{submission.no_feedback}"></span>
                     </div>
-                </div>
-            </div>
-        </div>
-        <div th:id="|script-feedback-${submission.id}|" class="hidden flex_horizontal" th:unless="${submission.scriptResults.isEmpty()}" th:with="trainResult = ${submission.scriptResults[0]}">
-            <select aria-label="Select wagon" style="margin-top: .5rem;" th:data-select="${submission.id}" class="selectbox">
-                <option th:each="wagonResult : ${trainResult.subresults}" th:value="${wagonResult.id}" th:text="|${wagonResult.wagon.name} (#{|script.status.${#strings.toLowerCase(wagonResult.status)}|})|"></option>
-            </select>
-            <div th:id="|${submission.id}-wagon-${wagonResult.id}-feedback|" th:each="wagonResult, iter : ${trainResult.subresults}" class="flex_grow" th:classappend="${iter.index == 0 ? '' : 'hidden'}">
-                <th:block th:each="scriptResult : ${wagonResult.subresults}">
-                    <div th:data-item="${scriptResult.id}" class="collapsed list_item">
-                        <div class="list_item_content fs_400">
-                            <span th:text="|${scriptResult.script.name} (#{|script.status.${#strings.toLowerCase(scriptResult.status)}|})|"></span>
-                            <span th:unless="${scriptResult.score == null}" class="tag" style="float: none;" th:text="${@gradeService.getScoreDisplayString(scriptResult.score, scriptResult.script.gradeScheme)}"></span>
+                    <th:block
+                        th:each="feedback : ${submission.feedback}"
+                        th:if="${@authorizationService.canViewGroupFeedback(submission.group.id, feedback)}">
+                        <div
+                            th:data-item="${feedback.id}"
+                            class="space-between flex accordion__header pil-3 pbl-3">
+                            <div>
+                                <span
+                                    th:unless="${feedback.isScriptFeedback}"
+                                    th:text="#{submission.feedback_by(${@personCacheManager.getOrThrow(feedback.authorId)?.displayName})}"></span>
+                                <span
+                                    class="chip"
+                                    th:if="${feedback.gradeId}"
+                                    th:text="${@gradeService.getScoreDisplayString(grades[feedback.gradeId])}"></span>
+                            </div>
+                            <div>
+                                <button
+                                    class="fa-solid fa-chevron-down"
+                                    type="button"
+                                    aria-expanded="false"
+                                    th:aria-controls="|staff-feedback-${feedback.id}|"></button>
+                            </div>
                         </div>
-                        <div class="list_actions">
-                            <button class="text-button fas fa-chevron-down" type="button" th:onclick="|toggleCollapse('${submission.id}-wagon-${wagonResult.id}-feedback', '${scriptResult.id}')|"></button>
+                        <div
+                            class="accordion__content pil-5 mb-3 flex vertical gap-2"
+                            th:id="|staff-feedback-${feedback.id}|">
+                            <p
+                                style="max-width: 80ch"
+                                th:each="text : ${#strings.listSplit(feedback.textualFeedback, '&#10;')}"
+                                th:text="${text}"></p>
                         </div>
-                    </div>
-                    <div class="list_subitems">
-                        <div th:each="feedback : ${scriptResult.feedback}" th:if="${@authorizationService.canViewGroupFeedback(group.id, feedback)}" class="list_subitem">
-                            <div class="list_subitem_content fs_300">
-                                <p style="max-width: 80ch;" th:each="text : ${#strings.listSplit(feedback.feedback, '&#10;')}" th:text="${text}"></p>
+                    </th:block>
+                </div>
+
+                <div
+                    th:id="|script-feedback-${submission.id}|"
+                    th:unless="${submission.scriptResults.isEmpty()}"
+                    th:with="trainResult = ${submission.scriptResults[0]}"
+                    hidden>
+                    <div class="accordion">
+                        <th:block th:each="wagonResult, iter : ${trainResult.subresults}">
+                            <div class="accordion__header flex space-between pil-5 pbl-3">
+                                <div class="flex gap-3 align-center">
+                                    <span
+                                        th:text="|${wagonResult.wagon.name} (#{|script.status.${wagonResult.status.name().toLowerCase()}|})|"></span>
+                                    <span
+                                        class="chip"
+                                        th:if="${wagonResult.status.name() == 'DONE' and wagonResult.score != null}"
+                                        th:text="${@gradeService.getScoreDisplayString(wagonResult.score, wagonResult.wagon.gradeScheme)}"></span>
+                                </div>
+                                <div>
+                                    <button
+                                        type="button"
+                                        aria-expanded="false"
+                                        th:aria-controls="|wagon-${wagonResult.wagon.id}|"
+                                        class="fa-solid fa-chevron-down"></button>
+                                </div>
                             </div>
-                        </div>
+
+                            <div
+                                class="accordion__content flex gap-3 vertical mb-3"
+                                th:id="|wagon-${wagonResult.wagon.id}|">
+                                <div
+                                    th:each="scriptResult : ${wagonResult.subresults}"
+                                    class="pil-5">
+                                    <div class="flex gap-3 align-center">
+                                        <span
+                                            th:text="|${scriptResult.script.name} (#{|script.status.${scriptResult.status.name().toLowerCase()}|})|"></span>
+                                        <span
+                                            class="chip"
+                                            th:if="${scriptResult.status.name() == 'DONE' and scriptResult.score != null}"
+                                            th:text="${@gradeService.getScoreDisplayString(scriptResult.score, scriptResult.script.gradeScheme)}"></span>
+                                    </div>
+
+                                    <div
+                                        th:if="${scriptResult.feedback.isEmpty()}"
+                                        class="font-200 mt-2">
+                                        <p th:if="${scriptResult.status.isFinished()}">
+                                            No feedback
+                                        </p>
+                                        <p th:unless="${scriptResult.status.isFinished()}">
+                                            No feedback yet
+                                        </p>
+                                    </div>
+                                    <th:block
+                                        th:each="feedback : ${scriptResult.feedback}"
+                                        th:if="${@authorizationService.canViewGroupFeedback(group.id, feedback)}">
+                                        <div class="font-200 mt-2">
+                                            <p
+                                                style="max-width: 100ch"
+                                                th:each="text : ${#strings.listSplit(feedback.feedback, '&#10;')}"
+                                                th:text="${text}"></p>
+                                        </div>
+                                    </th:block>
+                                </div>
+                            </div>
+                        </th:block>
                     </div>
-                </th:block>
-            </div>
-        </div>
-
-<!--            <span th:if="${submission.feedback.isEmpty()}" th:text="#{submission.no_feedback}"></span>-->
-<!--            <div th:each="feedback : ${submission.feedback}" th:if="${@authorizationService.canViewGroupFeedback(submission.group.id, feedback)}" th:remove="tag">-->
-<!--                <span th:if="${feedback.isScriptFeedback}" th:text="#{submission.feedback_from(${feedback.authorId == -1 ? 'script' : @scriptService.getScriptById(feedback.authorId).name})}"></span>-->
-<!--                <span th:unless="${feedback.isScriptFeedback}" th:text="#{submission.feedback_by(${@personCacheManager.getOrThrow(feedback.authorId)?.username})}"></span>-->
-<!--                <div>-->
-<!--                    <span class="tag" th:text="${feedback.gradeId == null} ? #{grading.no_grade} : ${@gradeService.getScoreDisplayString(grades[feedback.gradeId])}"></span>-->
-<!--                    <div>-->
-<!--                        <p th:each="text : ${#strings.listSplit(feedback.textualFeedback, '&#10;')}" th:text="${text}"></p>-->
-<!--                    </div>-->
-<!--                </div>-->
-<!--            </div>-->
-
-        <div th:if="${@authorizationService.canGradeSubmission(submission.id)}" class="form-buttons">
-            <button type=button class="warning text-button" th:text="#{general.cancel}"
-                    th:onclick="|toggleOverlay('feedback-${submission.id}-overlay')|"></button>
-            <button type=submit class="text-button" th:text="#{general.save}"></button>
-        </div>
-        <div th:unless="${@authorizationService.canGradeSubmission(submission.id)}" class="form-buttons">
-            <span></span>
-            <button type=button class="text-button" th:text="#{general.close}"
-                    th:onclick="|toggleOverlay('feedback-${submission.id}-overlay')|"></button>
-        </div>
-    </form>
-</div>
-
-<script th:fragment="script">
-    document.addEventListener("DOMContentLoaded", function () {
-        $("[data-select]").change(function () {
-            $(this).parent().children(":not(select)").addClass("hidden");
-            $(`#${$(this).data("select")}-wagon-${$(this).val()}-feedback`).removeClass("hidden");
-        });
-
-        $("[data-tab]").click(function () {
-            $(this).parent().children().each((i, e) => {
-                $(e).removeClass("active");
-                $(`#${$(e).data("tab")}`).addClass("hidden");
-            });
-            $(this).addClass("active");
-            $(`#${$(this).data("tab")}`).removeClass("hidden")
-        });
-    });
-</script>
-
-</body>
-</html>
\ No newline at end of file
+                </div>
+
+                <div
+                    th:if="${@authorizationService.canGradeSubmission(submission.id)}"
+                    class="flex space-between">
+                    <button
+                        type="button"
+                        class="button p-less"
+                        data-style="outlined"
+                        th:text="#{general.cancel}"
+                        data-cancel></button>
+                    <button type="submit" class="button p-less" th:text="#{general.save}"></button>
+                </div>
+                <div
+                    th:unless="${@authorizationService.canGradeSubmission(submission.id)}"
+                    class="flex justify-end">
+                    <button
+                        type="button"
+                        data-style="outlined"
+                        class="button p-less"
+                        th:text="#{general.close}"
+                        data-cancel></button>
+                </div>
+            </form>
+        </dialog>
+    </body>
+</html>
diff --git a/src/main/resources/templates/submission/note.html b/src/main/resources/templates/submission/note.html
index d270fc94d460e8c7e9d882b913bff4a756bc6446..79100648d285df64c79ede9a2638e0b0e91f3b90 100644
--- a/src/main/resources/templates/submission/note.html
+++ b/src/main/resources/templates/submission/note.html
@@ -18,27 +18,51 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
-
-<div th:fragment="overlay" th:if="${@authorizationService.canEditSubmissionNote(submission.id)}" th:id="|note-${submission.id}-overlay|" class="hidden overlay">
-    <form th:object="${submissionNote}" th:action="@{|/submission/note/${group.id}|}" method="post" class="boxed-content">
-        <h1 class="underlined title" th:text="#{submission.note}"></h1>
-        <div class="form-group">
-            <input type="hidden" th:name="entityId.submissionId" th:value="${submission.id}"/>
-            <span th:text="#{general.for}"></span>
-            <span th:text="|${group.name}, ${assignment.name} - #{submission.name(${submissions.size() - iter.index})}|"></span>
-            <label for="note" th:text="#{general.note}"></label>
-            <textarea id="note" th:name="note" th:placeholder="#{general.note.enter}" th:text="${submission.note?.note}"
-                      rows="6" type="text" class="textarea" required></textarea>
-        </div>
-        <div class="form-buttons">
-            <button type=button class="warning text-button" th:text="#{general.cancel}"
-                    th:onclick="|toggleOverlay('note-${submission.id}-overlay')|"></button>
-            <button type=submit class="text-button" th:text="#{general.save}"></button>
-        </div>
-    </form>
-</div>
-
-</body>
-</html>
\ No newline at end of file
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <dialog
+            th:fragment="overlay"
+            th:if="${@authorizationService.canEditSubmissionNote(submission.id)}"
+            th:id="|note-${submission.id}-overlay|"
+            class="dialog">
+            <form
+                th:object="${submissionNote}"
+                th:action="@{|/submission/note/${group.id}|}"
+                method="post"
+                class="flex vertical p-7">
+                <h1 class="underlined font-500" th:text="#{submission.note}"></h1>
+                <div class="grid col-2 gap-3" style="--col-1: minmax(0, 8rem)">
+                    <input
+                        type="hidden"
+                        th:name="entityId.submissionId"
+                        th:value="${submission.id}" />
+                    <span th:text="#{general.for}"></span>
+                    <span
+                        th:text="|${group.name}, ${assignment.name} - #{submission.name(${submissions.size() - iter.index})}|"></span>
+                    <label for="note" th:text="#{general.note}"></label>
+                    <textarea
+                        id="note"
+                        th:name="note"
+                        th:placeholder="#{general.note.enter}"
+                        th:text="${submission.note?.note}"
+                        rows="6"
+                        type="text"
+                        class="textfield"
+                        required></textarea>
+                </div>
+                <div class="flex space-between">
+                    <button
+                        type="button"
+                        class="button p-less"
+                        data-style="outlined"
+                        th:text="#{general.cancel}"
+                        data-cancel></button>
+                    <button type="submit" class="button p-less" th:text="#{general.save}"></button>
+                </div>
+            </form>
+        </dialog>
+    </body>
+</html>
diff --git a/src/main/resources/templates/submission/remove_grade.html b/src/main/resources/templates/submission/remove_grade.html
index 34316753000adc12379b42551873027ba39e2070..772c7665863c350c3bed3552c3e71b6e6cffaf3f 100644
--- a/src/main/resources/templates/submission/remove_grade.html
+++ b/src/main/resources/templates/submission/remove_grade.html
@@ -18,19 +18,35 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
-
-<div th:fragment="overlay" th:id="|remove-grade-${submission.id}-overlay|" class="hidden confirm overlay">
-    <form th:action="@{|/grade/assignment/remove-highest/${assignment.id}/${submission.id}|}" method="post" class="boxed-content">
-        <p class="confirm_text" th:text="#{submission.remove_grade.confirm}"></p>
-        <div class="form-buttons">
-            <button type=button class="warning text-button" th:text="#{general.cancel}"
-                    th:onclick="|toggleOverlay('remove-grade-${submission.id}-overlay')|"></button>
-            <button type=submit class="text-button" th:text="#{general.remove}"></button>
-        </div>
-    </form>
-</div>
-
-</body>
-</html>
\ No newline at end of file
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <dialog
+            th:fragment="overlay"
+            th:id="|remove-grade-${submission.id}-overlay|"
+            class="dialog">
+            <form
+                th:action="@{|/grade/assignment/remove-highest/${assignment.id}/${submission.id}|}"
+                method="post"
+                class="flex vertical p-7">
+                <h2 class="underlined font-500">Remove grade</h2>
+                <p th:text="#{submission.remove_grade.confirm}"></p>
+                <div class="flex space-between">
+                    <button
+                        type="button"
+                        class="button p-less"
+                        data-style="outlined"
+                        th:text="#{general.cancel}"
+                        data-cancel></button>
+                    <button
+                        type="submit"
+                        class="button p-less"
+                        data-type="error"
+                        th:text="#{general.remove}"></button>
+                </div>
+            </form>
+        </dialog>
+    </body>
+</html>
diff --git a/src/main/resources/templates/version/add.html b/src/main/resources/templates/version/add.html
index 818f0be4bbe5be53ef067488895c7fed29b132b2..f4ac3b8ad517d96047ae4cca4d11aa65278842ed 100644
--- a/src/main/resources/templates/version/add.html
+++ b/src/main/resources/templates/version/add.html
@@ -18,41 +18,73 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <dialog th:fragment="overlay" id="add-version-overlay" class="dialog">
+            <form
+                th:object="${version}"
+                th:action="@{/version}"
+                method="post"
+                class="flex vertical p-7">
+                <h1 class="underlined font-500" th:text="#{version.add}"></h1>
 
-<div th:fragment="overlay" id="add-version-overlay" class="hidden overlay">
-    <form th:object="${version}" th:action="@{/version}" method="post" class="boxed-content">
-        <h1 class="underlined title" th:text="#{version.add}"></h1>
-        <div class="form-group">
-            <input type="hidden" th:name="assignment.id" th:value="${assignment.id}"/>
+                <div class="grid col-2 gap-3" style="--col-1: minmax(0, 8rem)">
+                    <input type="hidden" th:name="assignment.id" th:value="${assignment.id}" />
 
-            <label for="version-name" th:text="#{general.name}"></label>
-            <input id="version-name" th:name="name" th:placeholder="#{general.name.enter}" type="text" class="textfield" required/>
+                    <label for="version-name" th:text="#{general.name}"></label>
+                    <input
+                        id="version-name"
+                        th:name="name"
+                        th:placeholder="#{general.name.enter}"
+                        type="text"
+                        class="textfield"
+                        required />
 
-            <label for="version-description" th:text="#{general.description}"></label>
-            <textarea id="version-description" th:name="description.text" class="textarea" th:placeholder="#{general.description.enter}"></textarea>
+                    <label for="version-description" th:text="#{general.description}"></label>
+                    <textarea
+                        id="version-description"
+                        th:name="description.text"
+                        class="textfield"
+                        th:placeholder="#{general.description.enter}"></textarea>
 
-            <label for="version-deadline" th:text="#{assignment.deadline}"></label>
-            <input id="version-deadline" th:name="deadline" th:placeholder="#{assignment.deadline.enter}" type="text" class="textfield"
-                   pattern="|[0-3]\d-[0-1]\d-\d\d\d\d [0-2]\d:[0-5]\d">
+                    <label for="version-deadline" th:text="#{assignment.deadline}"></label>
+                    <input
+                        id="version-deadline"
+                        th:name="deadline"
+                        th:placeholder="#{assignment.deadline.enter}"
+                        type="text"
+                        class="textfield"
+                        pattern="|[0-3]\d-[0-1]\d-\d\d\d\d [0-2]\d:[0-5]\d" />
 
-            <div class="form-separator"></div>
+                    <div class="underlined" style="grid-column: span 2"></div>
 
-            <span th:text="#{module.groups}"></span>
-            <div class="form-hstack-grid">
-                <div class="checkbox" th:each="group: ${availableGroups}">
-                    <input th:id="|version-group-${group.id}|" th:name="|groups[${group.id}]|" type="checkbox"/>
-                    <label th:for="|version-group-${group.id}|" th:text="${group.name}"></label>
+                    <span th:text="#{module.groups}"></span>
+                    <div class="form-hstack-grid">
+                        <div class="checkbox" th:each="group: ${availableGroups}">
+                            <input
+                                th:id="|version-group-${group.id}|"
+                                th:name="|groups[${group.id}]|"
+                                type="checkbox" />
+                            <label
+                                th:for="|version-group-${group.id}|"
+                                th:text="${group.name}"></label>
+                        </div>
+                    </div>
                 </div>
-            </div>
-        </div>
-        <div class="form-buttons">
-            <button type=button class="warning text-button" th:text="#{general.cancel}" onclick="toggleOverlay('add-version-overlay')"></button>
-            <button type=submit class="text-button" th:text="#{general.save}"></button>
-        </div>
-    </form>
-</div>
-
-</body>
+
+                <div class="flex space-between">
+                    <button
+                        type="button"
+                        class="button p-less"
+                        data-style="outlined"
+                        th:text="#{general.cancel}"
+                        data-cancel></button>
+                    <button type="submit" class="button p-less" th:text="#{general.save}"></button>
+                </div>
+            </form>
+        </dialog>
+    </body>
 </html>
diff --git a/src/main/resources/templates/version/add_script.html b/src/main/resources/templates/version/add_script.html
index 5ef44a878b6e658b50a8b90342adcb9a27fcff04..a793199d20f276b616a45548bda50a3226b50b27 100644
--- a/src/main/resources/templates/version/add_script.html
+++ b/src/main/resources/templates/version/add_script.html
@@ -18,41 +18,90 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
-
-<div th:fragment="overlay" th:if="${@authorizationService.canEditScriptWagon(wagon.id)}" th:id="|add-script-${wagon.id}-overlay|" class="hidden overlay">
-    <form th:object="${script}" th:action="@{/script/script}" method="post" class="boxed-content">
-        <h1 class="underlined title" th:text="#{script.script.add}"></h1>
-        <div class="form-group">
-            <input type="hidden" th:name="wagon.id" th:value="${wagon.id}"/>
-
-            <label th:for="|${wagon.id}-script-name|" th:text="#{general.name}"></label>
-            <input th:id="|${wagon.id}-script-name|" th:name="name" th:placeholder="#{general.name.enter}" type="text" class="textfield" required/>
-
-            <label th:for="|${wagon.id}-script-link|" th:text="#{script.link}"></label>
-            <input th:id="|${wagon.id}-script-link|" th:name="link" th:placeholder="#{script.link.enter}" type="url" class="textfield" required/>
-
-            <label th:for="|${wagon.id}-script-scheme|" th:text="#{grading.score_type}"></label>
-            <select th:id="|${wagon.id}-script-scheme|" th:name="gradeScheme" class="selectbox" required>
-                <option th:each="scheme : ${T(nl.tudelft.labracore.lib.api.GradeScheme).values()}" th:value="${scheme}" th:text="#{|grading.type.${#strings.toLowerCase(scheme.name())}|}"></option>
-            </select>
-
-            <label th:for="|${wagon.id}-script-weight|" th:text="#{grading.weight}"></label>
-            <input th:id="|${wagon.id}-script-weight|" th:name="weight" value="0" min="0" type="number" class="textfield" required>
-
-            <span></span>
-            <div class="checkbox">
-                <input th:id="|${wagon.id}-script-required|" th:name="required" type="checkbox"/>
-                <label th:for="|${wagon.id}-script-required|" th:text="#{grading.required}"></label>
-            </div>
-        </div>
-        <div class="form-buttons">
-            <button type=button class="warning text-button" th:text="#{general.cancel}" th:onclick="|toggleOverlay('add-script-${wagon.id}-overlay')|"></button>
-            <button type=submit class="text-button" th:text="#{general.save}"></button>
-        </div>
-    </form>
-</div>
-
-</body>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <dialog
+            th:fragment="overlay"
+            th:if="${@authorizationService.canEditScriptWagon(wagon.id)}"
+            th:id="|add-script-${wagon.id}-overlay|"
+            class="dialog">
+            <form
+                th:object="${script}"
+                th:action="@{/script/script}"
+                method="post"
+                class="flex vertical p-7">
+                <h1 class="underlined font-500" th:text="#{script.script.add}"></h1>
+
+                <div class="grid col-2 gap-3 align-center" style="--col-1: minmax(0, 8rem)">
+                    <input type="hidden" th:name="wagon.id" th:value="${wagon.id}" />
+
+                    <label th:for="|${wagon.id}-script-name|" th:text="#{general.name}"></label>
+                    <input
+                        th:id="|${wagon.id}-script-name|"
+                        th:name="name"
+                        th:placeholder="#{general.name.enter}"
+                        type="text"
+                        class="textfield"
+                        required />
+
+                    <label th:for="|${wagon.id}-script-link|" th:text="#{script.link}"></label>
+                    <input
+                        th:id="|${wagon.id}-script-link|"
+                        th:name="link"
+                        th:placeholder="#{script.link.enter}"
+                        type="url"
+                        class="textfield"
+                        required />
+
+                    <label
+                        th:for="|${wagon.id}-script-scheme|"
+                        th:text="#{grading.score_type}"></label>
+                    <select
+                        th:id="|${wagon.id}-script-scheme|"
+                        th:name="gradeScheme"
+                        class="textfield"
+                        required>
+                        <option
+                            th:each="scheme : ${T(nl.tudelft.labracore.lib.api.GradeScheme).values()}"
+                            th:value="${scheme}"
+                            th:text="#{|grading.type.${#strings.toLowerCase(scheme.name())}|}"></option>
+                    </select>
+
+                    <label th:for="|${wagon.id}-script-weight|" th:text="#{grading.weight}"></label>
+                    <input
+                        th:id="|${wagon.id}-script-weight|"
+                        th:name="weight"
+                        value="0"
+                        min="0"
+                        type="number"
+                        class="textfield"
+                        required />
+
+                    <span></span>
+                    <div class="checkbox">
+                        <input
+                            th:id="|${wagon.id}-script-required|"
+                            th:name="required"
+                            type="checkbox" />
+                        <label
+                            th:for="|${wagon.id}-script-required|"
+                            th:text="#{grading.required}"></label>
+                    </div>
+                </div>
+
+                <div class="flex space-between">
+                    <button
+                        type="button"
+                        class="button p-less"
+                        data-style="outlined"
+                        th:text="#{general.cancel}"
+                        data-cancel></button>
+                    <button type="submit" class="button p-less" th:text="#{general.save}"></button>
+                </div>
+            </form>
+        </dialog>
+    </body>
 </html>
diff --git a/src/main/resources/templates/version/add_wagon.html b/src/main/resources/templates/version/add_wagon.html
index c0b424adbcccba0fdc699ed4d225a1c166a47d21..e2baedcde8c3225f46928cbb83dfd5e09733196a 100644
--- a/src/main/resources/templates/version/add_wagon.html
+++ b/src/main/resources/templates/version/add_wagon.html
@@ -18,38 +18,70 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
-
-<div th:fragment="overlay" th:if="${@authorizationService.canEditScriptTrain(train.id)}" id="add-wagon-overlay" class="hidden overlay">
-    <form th:object="${wagon}" th:action="@{/script/wagon}" method="post" class="boxed-content">
-        <h1 class="underlined title" th:text="#{script.wagon.add}"></h1>
-        <div class="form-group">
-            <input type="hidden" th:name="train.id" th:value="${train.id}"/>
-
-            <label for="wagon-name" th:text="#{general.name}"></label>
-            <input id="wagon-name" th:name="name" th:placeholder="#{general.name.enter}" type="text" class="textfield" required/>
-
-            <label for="wagon-scheme" th:text="#{grading.score_type}"></label>
-            <select id="wagon-scheme" th:name="gradeScheme" class="selectbox" required>
-                <option th:each="scheme : ${T(nl.tudelft.labracore.lib.api.GradeScheme).values()}" th:value="${scheme}" th:text="#{|grading.type.${#strings.toLowerCase(scheme.name())}|}"></option>
-            </select>
-
-            <label for="wagon-weight" th:text="#{grading.weight}"></label>
-            <input id="wagon-weight" th:name="weight" value="0" min="0" type="number" class="textfield" required>
-
-            <span></span>
-            <div class="checkbox">
-                <input id="wagon-required" th:name="required" type="checkbox"/>
-                <label for="wagon-required" th:text="#{grading.required}"></label>
-            </div>
-        </div>
-        <div class="form-buttons">
-            <button type=button class="warning text-button" th:text="#{general.cancel}" onclick="toggleOverlay('add-wagon-overlay')"></button>
-            <button type=submit class="text-button" th:text="#{general.save}"></button>
-        </div>
-    </form>
-</div>
-
-</body>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <dialog
+            th:fragment="overlay"
+            th:if="${@authorizationService.canEditScriptTrain(train.id)}"
+            id="add-wagon-overlay"
+            class="dialog">
+            <form
+                th:object="${wagon}"
+                th:action="@{/script/wagon}"
+                method="post"
+                class="flex vertical p-7">
+                <h1 class="underlined font-500" th:text="#{script.wagon.add}"></h1>
+
+                <div class="grid col-2 align-center gap-3" style="--col-1: minmax(0, 10rem)">
+                    <input type="hidden" th:name="train.id" th:value="${train.id}" />
+
+                    <label for="wagon-name" th:text="#{general.name}"></label>
+                    <input
+                        id="wagon-name"
+                        th:name="name"
+                        th:placeholder="#{general.name.enter}"
+                        type="text"
+                        class="textfield"
+                        required />
+
+                    <label for="wagon-scheme" th:text="#{grading.score_type}"></label>
+                    <select id="wagon-scheme" th:name="gradeScheme" class="textfield" required>
+                        <option
+                            th:each="scheme : ${T(nl.tudelft.labracore.lib.api.GradeScheme).values()}"
+                            th:value="${scheme}"
+                            th:text="#{|grading.type.${#strings.toLowerCase(scheme.name())}|}"></option>
+                    </select>
+
+                    <label for="wagon-weight" th:text="#{grading.weight}"></label>
+                    <input
+                        id="wagon-weight"
+                        th:name="weight"
+                        value="0"
+                        min="0"
+                        type="number"
+                        class="textfield"
+                        required />
+
+                    <span></span>
+                    <div class="checkbox">
+                        <input id="wagon-required" th:name="required" type="checkbox" />
+                        <label for="wagon-required" th:text="#{grading.required}"></label>
+                    </div>
+                </div>
+
+                <div class="flex space-between">
+                    <button
+                        type="button"
+                        class="button p-less"
+                        data-style="outlined"
+                        th:text="#{general.cancel}"
+                        data-cancel></button>
+                    <button type="submit" class="button p-less" th:text="#{general.save}"></button>
+                </div>
+            </form>
+        </dialog>
+    </body>
 </html>
diff --git a/src/main/resources/templates/version/edit_script.html b/src/main/resources/templates/version/edit_script.html
index 38e590abee7cd5c22d8b54315f7103e5f86fc7e6..30e376465ca35c934512c8757765db1cb590888c 100644
--- a/src/main/resources/templates/version/edit_script.html
+++ b/src/main/resources/templates/version/edit_script.html
@@ -18,39 +18,94 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
-
-<div th:fragment="overlay" th:if="${@authorizationService.canEditScript(script.id)}" th:id="|edit-script-${script.id}-overlay|" class="hidden overlay">
-    <form th:object="${scriptPatch}" th:action="@{|/script/script/${script.id}|}" th:method="patch" class="boxed-content">
-        <h1 class="underlined title" th:text="#{script.script.edit}"></h1>
-        <div class="form-group">
-            <label th:for="|script-${script.id}-name|" th:text="#{general.name}"></label>
-            <input th:id="|script-${script.id}-name|" th:name="name" th:value="${script.name}" th:placeholder="#{general.name.enter}" type="text" class="textfield" required/>
-
-            <label th:for="|script-${script.id}-link|" th:text="#{script.link}"></label>
-            <input th:id="|script-${script.id}-link|" th:name="link" th:value="${script.link}" th:placeholder="#{script.link.enter}" type="url" class="textfield" required/>
-
-            <label th:for="|script-${script.id}-scheme|" th:text="#{grading.score_type}"></label>
-            <select th:id="|script-${script.id}-scheme|" th:name="gradeScheme" class="selectbox" required>
-                <option th:each="scheme : ${T(nl.tudelft.labracore.lib.api.GradeScheme).values()}" th:selected="${script.gradeScheme == scheme}" th:value="${scheme}" th:text="#{|grading.type.${#strings.toLowerCase(scheme.name())}|}"></option>
-            </select>
-
-            <label th:for="|script-${script.id}-weight|" th:text="#{grading.weight}"></label>
-            <input th:id="|script-${script.id}-weight|" th:name="weight" th:value="${script.weight}" min="0" type="number" class="textfield" required>
-
-            <span></span>
-            <div class="checkbox">
-                <input th:id="|script-${script.id}-required|" th:name="required" th:checked="${script.required}" type="checkbox"/>
-                <label th:for="|script-${script.id}-required|" th:text="#{grading.required}"></label>
-            </div>
-        </div>
-        <div class="form-buttons">
-            <button type=button class="warning text-button" th:text="#{general.cancel}" th:onclick="|toggleOverlay('edit-script-${script.id}-overlay')|"></button>
-            <button type=submit class="text-button" th:text="#{general.save}"></button>
-        </div>
-    </form>
-</div>
-
-</body>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <dialog
+            th:fragment="overlay"
+            th:if="${@authorizationService.canEditScript(script.id)}"
+            th:id="|edit-script-${script.id}-overlay|"
+            class="dialog">
+            <form
+                th:object="${scriptPatch}"
+                th:action="@{|/script/script/${script.id}|}"
+                th:method="patch"
+                class="flex vertical p-7">
+                <h1 class="underlined font-500" th:text="#{script.script.edit}"></h1>
+
+                <div class="grid col-2 align-center gap-3" style="--col-1: minmax(0, 8rem)">
+                    <label th:for="|script-${script.id}-name|" th:text="#{general.name}"></label>
+                    <input
+                        th:id="|script-${script.id}-name|"
+                        th:name="name"
+                        th:value="${script.name}"
+                        th:placeholder="#{general.name.enter}"
+                        type="text"
+                        class="textfield"
+                        required />
+
+                    <label th:for="|script-${script.id}-link|" th:text="#{script.link}"></label>
+                    <input
+                        th:id="|script-${script.id}-link|"
+                        th:name="link"
+                        th:value="${script.link}"
+                        th:placeholder="#{script.link.enter}"
+                        type="url"
+                        class="textfield"
+                        required />
+
+                    <label
+                        th:for="|script-${script.id}-scheme|"
+                        th:text="#{grading.score_type}"></label>
+                    <select
+                        th:id="|script-${script.id}-scheme|"
+                        th:name="gradeScheme"
+                        class="textfield"
+                        required>
+                        <option
+                            th:each="scheme : ${T(nl.tudelft.labracore.lib.api.GradeScheme).values()}"
+                            th:selected="${script.gradeScheme == scheme}"
+                            th:value="${scheme}"
+                            th:text="#{|grading.type.${#strings.toLowerCase(scheme.name())}|}"></option>
+                    </select>
+
+                    <label
+                        th:for="|script-${script.id}-weight|"
+                        th:text="#{grading.weight}"></label>
+                    <input
+                        th:id="|script-${script.id}-weight|"
+                        th:name="weight"
+                        th:value="${script.weight}"
+                        min="0"
+                        type="number"
+                        class="textfield"
+                        required />
+
+                    <span></span>
+                    <div class="checkbox">
+                        <input
+                            th:id="|script-${script.id}-required|"
+                            th:name="required"
+                            th:checked="${script.required}"
+                            type="checkbox" />
+                        <label
+                            th:for="|script-${script.id}-required|"
+                            th:text="#{grading.required}"></label>
+                    </div>
+                </div>
+
+                <div class="flex space-between">
+                    <button
+                        type="button"
+                        class="button p-less"
+                        data-style="outlined"
+                        th:text="#{general.cancel}"
+                        data-cancel></button>
+                    <button type="submit" class="button p-less" th:text="#{general.save}"></button>
+                </div>
+            </form>
+        </dialog>
+    </body>
 </html>
diff --git a/src/main/resources/templates/version/edit_train.html b/src/main/resources/templates/version/edit_train.html
index 7247bfa310394f5bcbd9a8ed3a453c8d5b5131a1..01020d90bad9f5fadd0ab3f55757e8c8ed750c18 100644
--- a/src/main/resources/templates/version/edit_train.html
+++ b/src/main/resources/templates/version/edit_train.html
@@ -18,26 +18,47 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <dialog
+            th:fragment="overlay"
+            th:if="${@authorizationService.canEditScriptTrain(train.id)}"
+            id="edit-train-overlay"
+            class="dialog">
+            <form
+                th:action="@{|/script/train/${train.id}|}"
+                th:method="patch"
+                class="flex vertical p-7">
+                <h1 class="underlined font-500" th:text="#{script.train.edit}"></h1>
 
-<div th:fragment="overlay" th:if="${@authorizationService.canEditScriptTrain(train.id)}" id="edit-train-overlay" class="hidden overlay">
-    <form th:action="@{|/script/train/${train.id}|}" th:method="patch" class="boxed-content">
-        <h1 class="underlined title" th:text="#{script.train.edit}"></h1>
-        <div class="form-group">
-            <label for="train-grade-scheme" th:text="#{grading.score_type}"></label>
-            <select id="train-grade-scheme" th:name="gradeScheme" class="selectbox" required>
-                <option th:each="type : ${T(nl.tudelft.labracore.lib.api.GradeScheme).values()}"
-                        th:value="${type}" th:selected="${type == train.gradeScheme}"
-                        th:text="#{|grading.type.${#strings.toLowerCase(type.name())}|}"></option>
-            </select>
-        </div>
-        <div class="form-buttons">
-            <button type=button class="warning text-button" th:text="#{general.cancel}" onclick="toggleOverlay('edit-train-overlay')"></button>
-            <button type=submit class="text-button" th:text="#{general.save}"></button>
-        </div>
-    </form>
-</div>
+                <div class="grid col-2 align-center" style="--col-1: minmax(0, 8rem)">
+                    <label for="train-grade-scheme" th:text="#{grading.score_type}"></label>
+                    <select
+                        id="train-grade-scheme"
+                        th:name="gradeScheme"
+                        class="textfield"
+                        required>
+                        <option
+                            th:each="type : ${T(nl.tudelft.labracore.lib.api.GradeScheme).values()}"
+                            th:value="${type}"
+                            th:selected="${type == train.gradeScheme}"
+                            th:text="#{|grading.type.${#strings.toLowerCase(type.name())}|}"></option>
+                    </select>
+                </div>
 
-</body>
+                <div class="flex space-between">
+                    <button
+                        type="button"
+                        class="button p-less"
+                        data-style="outlined"
+                        th:text="#{general.cancel}"
+                        data-cancel></button>
+                    <button type="submit" class="button p-less" th:text="#{general.save}"></button>
+                </div>
+            </form>
+        </dialog>
+    </body>
 </html>
diff --git a/src/main/resources/templates/version/edit_wagon.html b/src/main/resources/templates/version/edit_wagon.html
index 9ce3d91053bd84d0600e3cd7a8238e64ca5df8ce..17ec7864253e3b6db752c8df276ca447ea5000c6 100644
--- a/src/main/resources/templates/version/edit_wagon.html
+++ b/src/main/resources/templates/version/edit_wagon.html
@@ -18,36 +18,82 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
-
-<div th:fragment="overlay" th:if="${@authorizationService.canEditScriptWagon(wagon.id)}" th:id="|edit-wagon-${wagon.id}-overlay|" class="hidden overlay">
-    <form th:object="${wagonPatch}" th:action="@{|/script/wagon/${wagon.id}|}" th:method="patch" class="boxed-content">
-        <h1 class="underlined title" th:text="#{script.wagon.edit}"></h1>
-        <div class="form-group">
-            <label th:for="|wagon-${wagon.id}-name|" th:text="#{general.name}"></label>
-            <input th:id="|wagon-${wagon.id}-name|" th:name="name" th:value="${wagon.name}" th:placeholder="#{general.name.enter}" type="text" class="textfield" required/>
-
-            <label th:for="|wagon-${wagon.id}-scheme|" th:text="#{grading.score_type}"></label>
-            <select th:id="|wagon-${wagon.id}-scheme|" th:name="gradeScheme" class="selectbox" required>
-                <option th:each="scheme : ${T(nl.tudelft.labracore.lib.api.GradeScheme).values()}" th:selected="${wagon.gradeScheme == scheme}" th:value="${scheme}" th:text="#{|grading.type.${#strings.toLowerCase(scheme.name())}|}"></option>
-            </select>
-
-            <label th:for="|wagon-${wagon.id}-weight|" th:text="#{grading.weight}"></label>
-            <input th:id="|wagon-${wagon.id}-weight|" th:name="weight" th:value="${wagon.weight}" min="0" type="number" class="textfield" required>
-
-            <span></span>
-            <div class="checkbox">
-                <input th:id="|wagon-${wagon.id}-required|" th:name="required" th:checked="${wagon.required}" type="checkbox"/>
-                <label th:for="|wagon-${wagon.id}-required|" th:text="#{grading.required}"></label>
-            </div>
-        </div>
-        <div class="form-buttons">
-            <button type=button class="warning text-button" th:text="#{general.cancel}" th:onclick="|toggleOverlay('edit-wagon-${wagon.id}-overlay')|"></button>
-            <button type=submit class="text-button" th:text="#{general.save}"></button>
-        </div>
-    </form>
-</div>
-
-</body>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <dialog
+            th:fragment="overlay"
+            th:if="${@authorizationService.canEditScriptWagon(wagon.id)}"
+            th:id="|edit-wagon-${wagon.id}-overlay|"
+            class="dialog">
+            <form
+                th:object="${wagonPatch}"
+                th:action="@{|/script/wagon/${wagon.id}|}"
+                th:method="patch"
+                class="flex vertical p-7">
+                <h1 class="underlined font-500" th:text="#{script.wagon.edit}"></h1>
+
+                <div class="grid col-2 gap-3 align-center" style="--col-1: minmax(0, 8rem)">
+                    <label th:for="|wagon-${wagon.id}-name|" th:text="#{general.name}"></label>
+                    <input
+                        th:id="|wagon-${wagon.id}-name|"
+                        th:name="name"
+                        th:value="${wagon.name}"
+                        th:placeholder="#{general.name.enter}"
+                        type="text"
+                        class="textfield"
+                        required />
+
+                    <label
+                        th:for="|wagon-${wagon.id}-scheme|"
+                        th:text="#{grading.score_type}"></label>
+                    <select
+                        th:id="|wagon-${wagon.id}-scheme|"
+                        th:name="gradeScheme"
+                        class="textfield"
+                        required>
+                        <option
+                            th:each="scheme : ${T(nl.tudelft.labracore.lib.api.GradeScheme).values()}"
+                            th:selected="${wagon.gradeScheme == scheme}"
+                            th:value="${scheme}"
+                            th:text="#{|grading.type.${#strings.toLowerCase(scheme.name())}|}"></option>
+                    </select>
+
+                    <label th:for="|wagon-${wagon.id}-weight|" th:text="#{grading.weight}"></label>
+                    <input
+                        th:id="|wagon-${wagon.id}-weight|"
+                        th:name="weight"
+                        th:value="${wagon.weight}"
+                        min="0"
+                        type="number"
+                        class="textfield"
+                        required />
+
+                    <span></span>
+                    <div class="checkbox">
+                        <input
+                            th:id="|wagon-${wagon.id}-required|"
+                            th:name="required"
+                            th:checked="${wagon.required}"
+                            type="checkbox" />
+                        <label
+                            th:for="|wagon-${wagon.id}-required|"
+                            th:text="#{grading.required}"></label>
+                    </div>
+                </div>
+
+                <div class="flex space-between">
+                    <button
+                        type="button"
+                        class="button p-less"
+                        data-style="outlined"
+                        th:text="#{general.cancel}"
+                        data-cancel></button>
+                    <button type="submit" class="button p-less" th:text="#{general.save}"></button>
+                </div>
+            </form>
+        </dialog>
+    </body>
 </html>
diff --git a/src/main/resources/templates/version/remove.html b/src/main/resources/templates/version/remove.html
index 436413757c364230a0297d7094115e441b180fdb..6520ce3c9119cb07ca0e7848f3e8f71b97bd756d 100644
--- a/src/main/resources/templates/version/remove.html
+++ b/src/main/resources/templates/version/remove.html
@@ -18,19 +18,32 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
-
-<div th:fragment="overlay" th:id="|remove-${version.id}-overlay|" class="hidden confirm overlay">
-    <form th:action="@{|/version/${version.id}/remove|}" method="post" class="boxed-content">
-        <p class="confirm_text" th:text="#{version.remove.confirm(${version.name})}"></p>
-        <div class="form-buttons">
-            <button type=button class="warning text-button" th:text="#{general.cancel}"
-                    th:onclick="|toggleOverlay('remove-${version.id}-overlay')|"></button>
-            <button type=submit class="text-button" th:text="#{general.remove}"></button>
-        </div>
-    </form>
-</div>
-
-</body>
-</html>
\ No newline at end of file
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <dialog th:fragment="overlay" th:id="|remove-${version.id}-overlay|" class="dialog">
+            <form
+                th:action="@{|/version/${version.id}/remove|}"
+                method="post"
+                class="flex vertical p-7">
+                <h2 class="underlined font-500">Remove version</h2>
+                <p th:text="#{version.remove.confirm(${version.name})}"></p>
+                <div class="flex space-between">
+                    <button
+                        type="button"
+                        class="button p-less"
+                        data-style="outlined"
+                        th:text="#{general.cancel}"
+                        data-cancel></button>
+                    <button
+                        type="submit"
+                        class="button p-less"
+                        data-type="error"
+                        th:text="#{general.remove}"></button>
+                </div>
+            </form>
+        </dialog>
+    </body>
+</html>
diff --git a/src/main/resources/templates/version/remove_script.html b/src/main/resources/templates/version/remove_script.html
index a5e53594815872cf9b201645990908d96e47cd29..00f7da9abe37457ee23ac3eae28f01076bdd3caa 100644
--- a/src/main/resources/templates/version/remove_script.html
+++ b/src/main/resources/templates/version/remove_script.html
@@ -18,19 +18,32 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
-
-<div th:fragment="overlay" th:id="|remove-script-${script.id}-overlay|" class="hidden confirm overlay">
-    <form th:action="@{|/script/script/${script.id}/remove|}" method="post" class="boxed-content">
-        <p class="confirm_text" th:text="#{script.remove_script.confirm(${script.name})}"></p>
-        <div class="form-buttons">
-            <button type=button class="warning text-button" th:text="#{general.cancel}"
-                    th:onclick="|toggleOverlay('remove-script-${wagon.id}-overlay')|"></button>
-            <button type=submit class="text-button" th:text="#{general.remove}"></button>
-        </div>
-    </form>
-</div>
-
-</body>
-</html>
\ No newline at end of file
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <dialog th:fragment="overlay" th:id="|remove-script-${script.id}-overlay|" class="dialog">
+            <form
+                th:action="@{|/script/script/${script.id}/remove|}"
+                method="post"
+                class="flex vertical p-7">
+                <h2 class="font-500 underlined">Remove script</h2>
+                <p th:text="#{script.remove_script.confirm(${script.name})}"></p>
+                <div class="flex space-between">
+                    <button
+                        type="button"
+                        class="button p-less"
+                        data-style="outlined"
+                        th:text="#{general.cancel}"
+                        data-cancel></button>
+                    <button
+                        type="submit"
+                        class="button p-less"
+                        data-type="error"
+                        th:text="#{general.remove}"></button>
+                </div>
+            </form>
+        </dialog>
+    </body>
+</html>
diff --git a/src/main/resources/templates/version/remove_wagon.html b/src/main/resources/templates/version/remove_wagon.html
index a49a77195cc3585969e42a60d549eff0cc41a598..6f53ef6a30036669a69156f2b97187df85b1487d 100644
--- a/src/main/resources/templates/version/remove_wagon.html
+++ b/src/main/resources/templates/version/remove_wagon.html
@@ -18,19 +18,32 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
-
-<div th:fragment="overlay" th:id="|remove-wagon-${wagon.id}-overlay|" class="hidden confirm overlay">
-    <form th:action="@{|/script/wagon/${wagon.id}/remove|}" method="post" class="boxed-content">
-        <p class="confirm_text" th:text="#{script.remove_wagon.confirm(${wagon.name})}"></p>
-        <div class="form-buttons">
-            <button type=button class="warning text-button" th:text="#{general.cancel}"
-                    th:onclick="|toggleOverlay('remove-wagon-${wagon.id}-overlay')|"></button>
-            <button type=submit class="text-button" th:text="#{general.remove}"></button>
-        </div>
-    </form>
-</div>
-
-</body>
-</html>
\ No newline at end of file
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <dialog th:fragment="overlay" th:id="|remove-wagon-${wagon.id}-overlay|" class="dialog">
+            <form
+                th:action="@{|/script/wagon/${wagon.id}/remove|}"
+                method="post"
+                class="flex vertical p-7">
+                <h2 class="font-500 underlined">Remove wagon</h2>
+                <p th:text="#{script.remove_wagon.confirm(${wagon.name})}"></p>
+                <div class="flex space-between">
+                    <button
+                        type="button"
+                        class="button p-less"
+                        data-style="outlined"
+                        th:text="#{general.cancel}"
+                        data-cancel></button>
+                    <button
+                        type="submit"
+                        class="button p-less"
+                        data-type="error"
+                        th:text="#{general.remove}"></button>
+                </div>
+            </form>
+        </dialog>
+    </body>
+</html>
diff --git a/src/main/resources/templates/version/train.html b/src/main/resources/templates/version/train.html
index 07759c98e22e160a5bfaa7cfa5425ec2849e39a1..45ee8f58b85cae7d22f6514b2596040c18bbf6d2 100644
--- a/src/main/resources/templates/version/train.html
+++ b/src/main/resources/templates/version/train.html
@@ -18,60 +18,73 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
-      layout:decorate="~{container}">
-<head>
-    <title th:text="#{script.train}"></title>
-</head>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+    layout:decorate="~{container}">
+    <head>
+        <title th:text="#{script.train}"></title>
+    </head>
 
-<body>
+    <body>
+        <div layout:fragment="content">
+            <div class="breadcrumbs mb-5">
+                <a th:href="@{/edition/enrolled}" th:text="#{edition.editions}"></a>
+                <span>&gt;</span>
+                <a
+                    th:href="@{|/edition/${assignment.module.edition.id}|}"
+                    th:text="|${edition.course.name} - ${edition.name}|"></a>
+                <span>&gt;</span>
+                <a
+                    th:if="${@authorizationService.canViewModule(assignment.module.id)}"
+                    th:href="@{|/module/${assignment.module.id}|}"
+                    th:text="${assignment.module.name}"></a>
+                <a
+                    th:unless="${@authorizationService.canViewModule(assignment.module.id)}"
+                    th:href="@{|/module/${assignment.module.id}/groups|}"
+                    th:text="${assignment.module.name}"></a>
+                <span>&gt;</span>
+                <a th:href="@{|/assignment/${assignment.id}|}" th:text="${assignment.name}"></a>
+                <span>&gt;</span>
+                <a th:href="@{|/script/train/${train.id}|}">Script train</a>
+            </div>
 
-<div layout:fragment="breadcrumbs">
-    <a th:href="@{/edition/enrolled}" th:text="#{edition.editions}"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{|/edition/${assignment.module.edition.id}|}" th:text="|${edition.course.name} - ${edition.name}|"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:if="${@authorizationService.canViewModule(assignment.module.id)}" th:href="@{|/module/${assignment.module.id}|}" th:text="${assignment.module.name}"></a>
-    <a th:unless="${@authorizationService.canViewModule(assignment.module.id)}" th:href="@{|/module/${assignment.module.id}/groups|}" th:text="${assignment.module.name}"></a>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{|/assignment/${assignment.id}|}" th:text="${assignment.name}"></a>
-    <div th:unless="${@assignmentService.isTransparent(assignment.id)}" th:remove="tag">
-        <span>&nbsp;&gt;&nbsp;</span>
-        <span th:text="${version.name}"></span>
-    </div>
-    <span>&nbsp;&gt;&nbsp;</span>
-    <a th:href="@{|/script/train/${train.id}|}" th:text="#{script.train}"></a>
-</div>
+            <div class="flex space-between align-center">
+                <h1 class="font-800" th:text="#{script.train}"></h1>
+                <div class="flex gap-3">
+                    <button
+                        class="button"
+                        data-style="outlined"
+                        data-dialog="edit-train-overlay"
+                        th:text="#{script.train.edit}"></button>
+                    <button
+                        class="button"
+                        data-style="outlined"
+                        data-dialog="add-wagon-overlay"
+                        th:text="#{script.wagon.add}"></button>
+                </div>
+            </div>
+            <div th:replace="~{version/wagon_list :: list}"></div>
 
-<div layout:fragment="sidebar">
-    <button class="sidebar_item" onclick="toggleOverlay('add-wagon-overlay')" th:text="#{script.wagon.add}"></button>
-</div>
+            <div th:replace="~{version/add_wagon :: overlay}"></div>
+            <div th:each="wagon : ${train.wagons}" th:remove="tag">
+                <div th:replace="~{version/add_script :: overlay}"></div>
+                <div th:replace="~{version/edit_wagon :: overlay}"></div>
+                <div th:replace="~{version/remove_wagon :: overlay}"></div>
+                <div th:each="script : ${wagon.scripts}" th:remove="tag">
+                    <div th:replace="~{version/edit_script :: overlay}"></div>
+                    <div th:replace="~{version/remove_script :: overlay}"></div>
+                </div>
+            </div>
 
-<div layout:fragment="content">
-
-    <h1 class="title" th:text="#{script.train}"></h1>
-    <div th:replace="~{version/wagon_list :: list}"></div>
-
-    <div th:replace="~{version/add_wagon :: overlay}"></div>
-    <div th:each="wagon : ${train.wagons}" th:remove="tag">
-        <div th:replace="~{version/add_script :: overlay}"></div>
-        <div th:replace="~{version/edit_wagon :: overlay}"></div>
-        <div th:replace="~{version/remove_wagon :: overlay}"></div>
-        <div th:each="script : ${wagon.scripts}" th:remove="tag">
-            <div th:replace="~{version/edit_script :: overlay}"></div>
-            <div th:replace="~{version/remove_script :: overlay}"></div>
+            <script>
+                function toggleCollapse(lId, aId) {
+                    const list = $(`#${lId}`);
+                    list.find(`.list_item:not([data-item="${aId}"])`).addClass("collapsed");
+                    list.find(`[data-item="${aId}"]`).toggleClass("collapsed");
+                }
+            </script>
         </div>
-    </div>
-
-    <script>
-        function toggleCollapse(lId, aId) {
-            const list = $(`#${lId}`);
-            list.find(`.list_item:not([data-item="${aId}"])`).addClass("collapsed");
-            list.find(`[data-item="${aId}"]`).toggleClass("collapsed");
-        }
-    </script>
-
-</div>
-
-</body>
-</html>
\ No newline at end of file
+    </body>
+</html>
diff --git a/src/main/resources/templates/version/wagon_list.html b/src/main/resources/templates/version/wagon_list.html
index 5ba36baea2e5700f7f436951ec21096a232052ea..f045395362d0e595d40651070088998e518f4586 100644
--- a/src/main/resources/templates/version/wagon_list.html
+++ b/src/main/resources/templates/version/wagon_list.html
@@ -18,37 +18,87 @@
 
 -->
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-<body>
-
-<div th:fragment="list" class="list">
-    <div th:if="${train.wagons.isEmpty()}" class="list_item">
-        <span class="list_item_content" th:text="#{script.no_wagons}"></span>
-    </div>
-    <div id="wagon-list" th:each="wagon : ${train.wagons}">
-        <div th:data-item="${wagon.id}" th:id="|${wagon.id}-listitem|" class="collapsed list_item">
-            <span class="list_item_content" th:text="${wagon.name}"></span>
-            <div class="list_actions">
-                <button class="text-button" th:onclick="|toggleOverlay('add-script-${wagon.id}-overlay')|" th:text="#{script.script.add}"></button>
-                <button class="text-button" th:onclick="|toggleOverlay('edit-wagon-${wagon.id}-overlay')|" th:text="#{general.edit}"></button>
-                <button class="warning text-button" th:onclick="|toggleOverlay('remove-wagon-${wagon.id}-overlay')|" th:text="#{general.remove}"></button>
-                <button class="text-button" th:onclick="|toggleCollapse('wagon-list', '${wagon.id}')|"><i class="fas fa-chevron-down"></i></button>
+<html
+    lang="en"
+    xmlns:th="http://www.thymeleaf.org"
+    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+    <body>
+        <div th:fragment="list" class="accordion">
+            <div th:if="${train.wagons.isEmpty()}" class="list_item">
+                <span class="list_item_content" th:text="#{script.no_wagons}"></span>
             </div>
-        </div>
-        <div class="list_subitems">
-            <span class="list_subitem" th:if="${wagon.scripts.isEmpty()}" th:text="#{script.no_scripts}"></span>
-            <div th:each="script : ${wagon.scripts}" class="list_subitem">
-                <div class="list_subitem_content">
-                    <span th:text="${script.name}"></span>
+            <th:block th:each="wagon : ${train.wagons}">
+                <div
+                    th:data-item="${wagon.id}"
+                    th:id="|${wagon.id}-listitem|"
+                    class="accordion__header flex space-between pil-5 pbl-3">
+                    <span th:text="${wagon.name}"></span>
+                    <div class="flex gap-5">
+                        <div class="flex gap-3 font-300">
+                            <button
+                                class="button p-min"
+                                data-style="outlined"
+                                th:data-dialog="|add-script-${wagon.id}-overlay|"
+                                th:text="#{script.script.add}"></button>
+                            <button
+                                class="button p-min"
+                                data-style="outlined"
+                                th:data-dialog="|edit-wagon-${wagon.id}-overlay|"
+                                th:text="#{general.edit}"></button>
+                            <button
+                                class="button p-min"
+                                data-type="error"
+                                data-style="outlined"
+                                th:data-dialog="|remove-wagon-${wagon.id}-overlay|"
+                                th:text="#{general.remove}"></button>
+                        </div>
+                        <button
+                            th:aria-controls="|wagon-${wagon.id}|"
+                            class="fas fa-chevron-down"></button>
+                    </div>
                 </div>
-                <div class="list_actions">
-                    <button class="text-button" th:onclick="|toggleOverlay('edit-script-${script.id}-overlay')|" th:text="#{general.edit}"></button>
-                    <button class="warning text-button" th:onclick="|toggleOverlay('remove-script-${script.id}-overlay')|" th:text="#{general.remove}"></button>
+
+                <div
+                    class="accordion__content flex vertical gap-3 mb-3"
+                    th:id="|wagon-${wagon.id}|">
+                    <span
+                        th:if="${wagon.scripts.isEmpty()}"
+                        th:text="#{script.no_scripts}"
+                        class="pil-5"></span>
+                    <div th:each="script : ${wagon.scripts}" class="flex space-between pil-5">
+                        <div>
+                            <span th:text="${script.name}"></span>
+                        </div>
+                        <div class="flex gap-3">
+                            <button
+                                class="button p-min"
+                                data-style="outlined"
+                                th:data-dialog="|edit-script-${script.id}-overlay|"
+                                th:text="#{general.edit}"></button>
+                            <button
+                                class="button p-min"
+                                data-style="outlined"
+                                data-type="error"
+                                th:data-dialog="|remove-script-${script.id}-overlay|"
+                                th:text="#{general.remove}"></button>
+                        </div>
+                    </div>
                 </div>
-            </div>
-        </div>
-    </div>
-</div>
+            </th:block>
 
-</body>
+            <th:block>
+                <div th:replace="~{version/add_wagon :: overlay}"></div>
+                <div th:replace="~{version/edit_train :: overlay}"></div>
+                <th:block th:each="wagon : ${train.wagons}">
+                    <div th:replace="~{version/add_script :: overlay}"></div>
+                    <div th:replace="~{version/edit_wagon :: overlay}"></div>
+                    <div th:replace="~{version/remove_wagon :: overlay}"></div>
+                    <th:block th:each="script : ${wagon.scripts}">
+                        <div th:replace="~{version/edit_script :: overlay}"></div>
+                        <div th:replace="~{version/remove_script :: overlay}"></div>
+                    </th:block>
+                </th:block>
+            </th:block>
+        </div>
+    </body>
 </html>
diff --git a/src/test/java/nl/tudelft/submit/DevSubmitApplication.java b/src/test/java/application/dev/DevSubmitApplication.java
similarity index 60%
rename from src/test/java/nl/tudelft/submit/DevSubmitApplication.java
rename to src/test/java/application/dev/DevSubmitApplication.java
index 0a3d3f54ddae3b2529493bb85c0bed43fe40ab30..9c952c4bfb8678e57f3a65fd8af30d5d7fccfbfe 100644
--- a/src/test/java/nl/tudelft/submit/DevSubmitApplication.java
+++ b/src/test/java/application/dev/DevSubmitApplication.java
@@ -15,15 +15,25 @@
  * You should have received a copy of the GNU Affero General Public License
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
-package nl.tudelft.submit;
+package application.dev;
+
+import nl.tudelft.labracore.lib.LabracoreApiConfig;
+import nl.tudelft.submit.SubmitApplication;
+import nl.tudelft.submit.test.TestUserDetailsService;
 
 import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.domain.EntityScan;
+import org.springframework.context.annotation.Import;
 import org.springframework.context.annotation.PropertySource;
-import org.springframework.context.annotation.PropertySources;
+import org.springframework.data.jpa.convert.threeten.Jsr310JpaConverters;
 
-@PropertySources({
-		@PropertySource("classpath:application.yaml"),
-		@PropertySource("classpath:application-dev.properties")
+@SpringBootApplication
+@PropertySource("classpath:application-dev.properties")
+@EntityScan(basePackageClasses = { SubmitApplication.class, Jsr310JpaConverters.class })
+@Import({
+		LabracoreApiConfig.class,
+		TestUserDetailsService.class
 })
 public class DevSubmitApplication extends SubmitApplication {
 	public static void main(String[] args) {
diff --git a/src/test/java/application/migration/db/MigrationSubmitApplication.java b/src/test/java/application/migration/db/MigrationSubmitApplication.java
new file mode 100644
index 0000000000000000000000000000000000000000..e933eb55843e1676f163affaa737f443996b17ff
--- /dev/null
+++ b/src/test/java/application/migration/db/MigrationSubmitApplication.java
@@ -0,0 +1,42 @@
+/*
+ * Submit
+ * Copyright (C) 2020 - Delft University of Technology
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+package application.migration.db;
+
+import nl.tudelft.labracore.lib.LabracoreApiConfig;
+import nl.tudelft.submit.SubmitApplication;
+import nl.tudelft.submit.test.TestUserDetailsService;
+
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.domain.EntityScan;
+import org.springframework.context.annotation.Import;
+import org.springframework.context.annotation.PropertySource;
+import org.springframework.context.annotation.PropertySources;
+import org.springframework.data.jpa.convert.threeten.Jsr310JpaConverters;
+
+@SpringBootApplication
+@PropertySources({
+		@PropertySource("classpath:application-test.properties"),
+		@PropertySource("classpath:application-migration.properties"),
+})
+@EntityScan(basePackageClasses = { SubmitApplication.class, Jsr310JpaConverters.class })
+@Import({
+		LabracoreApiConfig.class,
+		TestUserDetailsService.class
+})
+public class MigrationSubmitApplication extends SubmitApplication {
+}
diff --git a/src/test/java/application/migration/nodb/NoDbMigrationSubmitApplication.java b/src/test/java/application/migration/nodb/NoDbMigrationSubmitApplication.java
new file mode 100644
index 0000000000000000000000000000000000000000..9fe45b179b741ab3bd09c2b30885fac65a4a5ce3
--- /dev/null
+++ b/src/test/java/application/migration/nodb/NoDbMigrationSubmitApplication.java
@@ -0,0 +1,42 @@
+/*
+ * Submit
+ * Copyright (C) 2020 - Delft University of Technology
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+package application.migration.nodb;
+
+import nl.tudelft.labracore.lib.LabracoreApiConfig;
+import nl.tudelft.submit.SubmitApplication;
+import nl.tudelft.submit.test.TestUserDetailsService;
+
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.domain.EntityScan;
+import org.springframework.context.annotation.Import;
+import org.springframework.context.annotation.PropertySource;
+import org.springframework.context.annotation.PropertySources;
+import org.springframework.data.jpa.convert.threeten.Jsr310JpaConverters;
+
+@SpringBootApplication
+@PropertySources({
+		@PropertySource("classpath:application-test.properties"),
+		@PropertySource("classpath:application-migration.properties"),
+})
+@EntityScan(basePackageClasses = { SubmitApplication.class, Jsr310JpaConverters.class })
+@Import({
+		LabracoreApiConfig.class,
+		TestUserDetailsService.class
+})
+public class NoDbMigrationSubmitApplication extends SubmitApplication {
+}
diff --git a/src/test/java/application/nodb/NoDbTestSubmitApplication.java b/src/test/java/application/nodb/NoDbTestSubmitApplication.java
new file mode 100644
index 0000000000000000000000000000000000000000..4e67159efe3adb63f0f7fd5b1c2da8313403ecc1
--- /dev/null
+++ b/src/test/java/application/nodb/NoDbTestSubmitApplication.java
@@ -0,0 +1,38 @@
+/*
+ * Submit
+ * Copyright (C) 2020 - Delft University of Technology
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+package application.nodb;
+
+import nl.tudelft.labracore.lib.LabracoreApiConfig;
+import nl.tudelft.submit.SubmitApplication;
+import nl.tudelft.submit.test.TestUserDetailsService;
+
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.domain.EntityScan;
+import org.springframework.context.annotation.Import;
+import org.springframework.context.annotation.PropertySource;
+import org.springframework.data.jpa.convert.threeten.Jsr310JpaConverters;
+
+@SpringBootApplication
+@PropertySource("classpath:application-test.properties")
+@EntityScan(basePackageClasses = { SubmitApplication.class, Jsr310JpaConverters.class })
+@Import({
+		LabracoreApiConfig.class,
+		TestUserDetailsService.class
+})
+public class NoDbTestSubmitApplication extends SubmitApplication {
+}
diff --git a/src/test/java/application/test/TestSubmitApplication.java b/src/test/java/application/test/TestSubmitApplication.java
new file mode 100644
index 0000000000000000000000000000000000000000..6ae4c22ba747ab07582b55a9ab77c5615b74f056
--- /dev/null
+++ b/src/test/java/application/test/TestSubmitApplication.java
@@ -0,0 +1,38 @@
+/*
+ * Submit
+ * Copyright (C) 2020 - Delft University of Technology
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+package application.test;
+
+import nl.tudelft.labracore.lib.LabracoreApiConfig;
+import nl.tudelft.submit.SubmitApplication;
+import nl.tudelft.submit.test.TestUserDetailsService;
+
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.domain.EntityScan;
+import org.springframework.context.annotation.Import;
+import org.springframework.context.annotation.PropertySource;
+import org.springframework.data.jpa.convert.threeten.Jsr310JpaConverters;
+
+@SpringBootApplication
+@PropertySource("classpath:application-test.properties")
+@EntityScan(basePackageClasses = { SubmitApplication.class, Jsr310JpaConverters.class })
+@Import({
+		LabracoreApiConfig.class,
+		TestUserDetailsService.class
+})
+public class TestSubmitApplication extends SubmitApplication {
+}
diff --git a/src/test/java/nl/tudelft/submit/controller/AnnouncementControllerTest.java b/src/test/java/nl/tudelft/submit/controller/AnnouncementControllerTest.java
index 3b6a5292a4b0ec798b63192b1240a6f0c82f1673..f54c9ea66cfd1198a47a517924026670dd7df160 100644
--- a/src/test/java/nl/tudelft/submit/controller/AnnouncementControllerTest.java
+++ b/src/test/java/nl/tudelft/submit/controller/AnnouncementControllerTest.java
@@ -27,7 +27,6 @@ import java.time.LocalDate;
 import java.time.LocalTime;
 import java.util.List;
 
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.dto.create.announcement.EditionAnnouncementCreateDTO;
 import nl.tudelft.submit.dto.create.announcement.GlobalAnnouncementCreateDTO;
 import nl.tudelft.submit.model.announcement.EditionAnnouncement;
@@ -44,7 +43,11 @@ import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.security.test.context.support.WithUserDetails;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
+import org.springframework.transaction.annotation.Transactional;
 
+import application.test.TestSubmitApplication;
+
+@Transactional
 @AutoConfigureMockMvc
 @SpringBootTest(classes = TestSubmitApplication.class)
 public class AnnouncementControllerTest {
diff --git a/src/test/java/nl/tudelft/submit/controller/AssignmentControllerTest.java b/src/test/java/nl/tudelft/submit/controller/AssignmentControllerTest.java
index c6b60fff06bf4c046ea7165d1d6a4a49935588c2..af3fef49bc7d37f4033045597a3b879ef113b4f7 100644
--- a/src/test/java/nl/tudelft/submit/controller/AssignmentControllerTest.java
+++ b/src/test/java/nl/tudelft/submit/controller/AssignmentControllerTest.java
@@ -27,7 +27,6 @@ import java.util.*;
 
 import nl.tudelft.labracore.api.dto.*;
 import nl.tudelft.labracore.lib.api.GradeScheme;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.cache.PersonCacheManager;
 import nl.tudelft.submit.dto.create.SubmitAssignmentCreateDTO;
 import nl.tudelft.submit.dto.create.note.AssignmentNoteCreateDTO;
@@ -60,6 +59,8 @@ import org.springframework.security.test.context.support.WithUserDetails;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
 
+import application.test.TestSubmitApplication;
+
 import com.fasterxml.jackson.databind.ObjectMapper;
 
 @AutoConfigureMockMvc
@@ -324,6 +325,7 @@ class AssignmentControllerTest {
 	@WithUserDetails("username")
 	void getStatisticsAllowsIfCanViewAssignmentStatistics() throws Exception {
 		when(authService.canViewAssignmentStatistics(anyLong())).thenReturn(true);
+		when(assignmentService.getSubmitAssignmentDetails(anyLong())).thenReturn(SUBMIT_ASSIGNMENT_DETAILS);
 		when(assignmentService.getAssignmentModuleDetails(anyLong())).thenReturn(ASSIGNMENT_DETAILS);
 		when(statisticsService.getAssignmentStatistics(any())).thenReturn(AssignmentStatisticsDTO.builder()
 				.totalGroups(0).totalStudents(0).totalSubmissions(0).build());
@@ -337,35 +339,6 @@ class AssignmentControllerTest {
 		verify(statisticsService).getAssignmentStatistics(any());
 	}
 
-	@Test
-	@WithUserDetails("username")
-	void getStatisticsTableVerifiesCanViewAssignmentStatistics() throws Exception {
-		when(authService.canViewAssignmentStatistics(anyLong())).thenReturn(false);
-
-		mockMvc.perform(get("/assignment/{id}/statistics-table", ASSIGNMENT_ID))
-				.andExpect(status().isForbidden());
-
-		verify(authService).canViewAssignmentStatistics(ASSIGNMENT_ID);
-		verify(statisticsService, never()).getAssignmentTableData(any(), any(), any(), any());
-	}
-
-	@Test
-	@WithUserDetails("username")
-	void getStatisticsTableAllowsIfCanViewAssignmentStatistics() throws Exception {
-		when(authService.canViewAssignmentStatistics(anyLong())).thenReturn(true);
-		when(assignmentService.getAssignmentModuleDetails(anyLong())).thenReturn(ASSIGNMENT_DETAILS);
-		when(statisticsService.getAssignmentTableData(any(), any(), any(), any()))
-				.thenReturn(Collections.emptyList());
-		when(editionService.getEdition(anyLong()))
-				.thenReturn(new EditionDetailsDTO().course(new CourseSummaryDTO()));
-
-		mockMvc.perform(get("/assignment/{id}/statistics-table", ASSIGNMENT_ID))
-				.andExpect(status().isOk());
-
-		verify(authService).canViewAssignmentStatistics(ASSIGNMENT_ID);
-		verify(statisticsService).getAssignmentTableData(any(), any(), any(), any());
-	}
-
 	@Test
 	@WithUserDetails("username")
 	void patchAssignmentVerifiesCanEditAssignment() throws Exception {
@@ -507,7 +480,6 @@ class AssignmentControllerTest {
 				get("/assignment/1"),
 				get("/assignment/1/all"),
 				get("/assignment/1/statistics"),
-				get("/assignment/1/statistics-table"),
 				get("/assignment/create/1"),
 				get("/assignment/1/submissions"),
 				post("/assignment")
diff --git a/src/test/java/nl/tudelft/submit/controller/EditionControllerTest.java b/src/test/java/nl/tudelft/submit/controller/EditionControllerTest.java
index 657e292071fa97f672f1d276481c3636ee06aea8..804cda52ba33efe92b3b2583f59cc5d06f28d62a 100644
--- a/src/test/java/nl/tudelft/submit/controller/EditionControllerTest.java
+++ b/src/test/java/nl/tudelft/submit/controller/EditionControllerTest.java
@@ -28,7 +28,6 @@ import java.util.List;
 
 import nl.tudelft.labracore.api.dto.*;
 import nl.tudelft.labracore.lib.api.GradeScheme;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.csv.CSVService;
 import nl.tudelft.submit.dto.create.SubmitEditionCreateDTO;
 import nl.tudelft.submit.dto.create.grading.GradingFormulaCreateDTO;
@@ -61,6 +60,8 @@ import org.springframework.security.test.context.support.WithUserDetails;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
 
+import application.test.TestSubmitApplication;
+
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
 
@@ -90,7 +91,6 @@ public class EditionControllerTest {
 			.name("Test Edition")
 			.course(new CourseIdDTO().id(COURSE_ID))
 			.cohort(new CohortIdDTO().id(4956L))
-			.enrollability(EditionCreateDTO.EnrollabilityEnum.OPEN)
 			.startDate(LocalDateTime.now())
 			.endDate(LocalDateTime.now().plusDays(50L))
 			.build();
@@ -182,6 +182,7 @@ public class EditionControllerTest {
 		when(authService.canViewEdition(anyLong())).thenReturn(true);
 		when(editionService.getEdition(anyLong())).thenReturn(EDITION_VIEW);
 		when(authService.isStudentInEdition(anyLong())).thenReturn(false);
+		when(authService.canViewEditionManagerView(anyLong())).thenReturn(true);
 		when(editionService.getDetailedEditionView(any(EditionIdDTO.class)))
 				.thenReturn(DETAILED_EDITION_VIEW);
 
@@ -189,27 +190,38 @@ public class EditionControllerTest {
 				.andExpect(status().isOk())
 				.andExpect(model().attributeExists("edition"));
 
-		verify(authService).canViewEdition(EDITION_ID);
+		verify(authService, times(2)).canViewEdition(EDITION_ID);
 		verify(editionService).getDetailedEditionView(new EditionIdDTO().id(EDITION_ID));
 	}
 
 	@Test
 	@WithUserDetails("username")
-	void getEditionDetailsReturnsStudentWhenStudentRole() throws Exception {
+	void getEditionDetailsRedirectsAssistants() throws Exception {
 		when(authService.canViewEdition(anyLong())).thenReturn(true);
-		when(authService.isStudentInEdition(anyLong())).thenReturn(true);
 		when(editionService.getEdition(anyLong())).thenReturn(EDITION_VIEW);
+		when(authService.canViewEditionManagerView(anyLong())).thenReturn(false);
+		when(authService.canAssistEdition(anyLong())).thenReturn(true);
 		when(editionService.getDetailedEditionView(any(EditionIdDTO.class)))
 				.thenReturn(DETAILED_EDITION_VIEW);
 
 		mockMvc.perform(get("/edition/{id}", EDITION_ID))
-				.andExpect(status().isOk())
-				.andExpect(model().attributeExists("edition"))
-				.andReturn();
+				.andExpect(status().is3xxRedirection())
+				.andExpect(redirectedUrl("/edition/" + EDITION_ID + "/assist"));
+	}
 
-		verify(authService).canViewEdition(EDITION_ID);
-		verify(authService, times(7)).isStudentInEdition(EDITION_ID);
-		verify(editionService).getDetailedEditionView(new EditionIdDTO().id(EDITION_ID));
+	@Test
+	@WithUserDetails("username")
+	void getEditionDetailsRedirectsStudents() throws Exception {
+		when(authService.canViewEdition(anyLong())).thenReturn(true);
+		when(editionService.getEdition(anyLong())).thenReturn(EDITION_VIEW);
+		when(authService.canViewEditionManagerView(anyLong())).thenReturn(false);
+		when(authService.canAssistEdition(anyLong())).thenReturn(false);
+		when(editionService.getDetailedEditionView(any(EditionIdDTO.class)))
+				.thenReturn(DETAILED_EDITION_VIEW);
+
+		mockMvc.perform(get("/edition/{id}", EDITION_ID))
+				.andExpect(status().is3xxRedirection())
+				.andExpect(redirectedUrl("/edition/" + EDITION_ID + "/student"));
 	}
 
 	@Test
@@ -231,11 +243,12 @@ public class EditionControllerTest {
 		when(roleService.getStudentRolesOfEdition(anyLong(), anyBoolean()))
 				.thenReturn(Collections.emptyList());
 		when(editionService.getEdition(anyLong())).thenReturn(EDITION_VIEW);
+		when(editionService.getDetailedEditionView(any())).thenReturn(DETAILED_EDITION_VIEW);
 
 		mockMvc.perform(get("/edition/{id}/students", EDITION_ID))
 				.andExpect(status().isOk());
 
-		verify(authService).canViewEditionOverview(EDITION_ID);
+		verify(authService, atLeastOnce()).canViewEditionOverview(EDITION_ID);
 		verify(editionService).getEdition(EDITION_ID);
 	}
 
@@ -256,7 +269,7 @@ public class EditionControllerTest {
 	void getEditionStaffAllowsIfCanViewEditionStaff() throws Exception {
 		when(authService.canViewEdition(anyLong())).thenReturn(true);
 		when(authService.isAtLeastTAInEdition(anyLong())).thenReturn(true);
-		when(editionService.getEdition(anyLong())).thenReturn(EDITION_VIEW);
+		when(editionService.getDetailedEditionView(any())).thenReturn(DETAILED_EDITION_VIEW);
 		when(authService.canViewEditionStaff(anyLong())).thenReturn(true);
 		when(roleService.getStudentRolesOfEdition(anyLong(), anyBoolean()))
 				.thenReturn(Collections.emptyList());
@@ -264,8 +277,7 @@ public class EditionControllerTest {
 		mockMvc.perform(get("/edition/{id}/staff", EDITION_ID))
 				.andExpect(status().isOk());
 
-		verify(authService).canViewEditionStaff(EDITION_ID);
-		verify(editionService).getEdition(anyLong());
+		verify(authService, atLeastOnce()).canViewEditionStaff(EDITION_ID);
 		verify(roleService).getStaffRolesOfEdition(EDITION_ID, true);
 	}
 
@@ -285,7 +297,7 @@ public class EditionControllerTest {
 	@WithUserDetails("username")
 	void getStatisticsAllowsIfCanViewEditionStatistics() throws Exception {
 		when(authService.canViewEditionStatistics(anyLong())).thenReturn(true);
-		when(editionService.getEdition(anyLong())).thenReturn(EDITION_VIEW);
+		when(editionService.getDetailedEditionView(any())).thenReturn(DETAILED_EDITION_VIEW);
 		when(statisticsService.getEditionStatistics(any()))
 				.thenReturn(EditionStatisticsDTO.builder().totalStudents(0).build());
 
@@ -296,33 +308,6 @@ public class EditionControllerTest {
 		verify(statisticsService).getEditionStatistics(any());
 	}
 
-	@Test
-	@WithUserDetails("username")
-	void getStatisticsTableVerifiesCanViewEditionStatistics() throws Exception {
-		when(authService.canViewEditionStatistics(anyLong())).thenReturn(false);
-
-		mockMvc.perform(get("/edition/{id}/statistics-table", EDITION_ID))
-				.andExpect(status().isForbidden());
-
-		verify(authService).canViewEditionStatistics(EDITION_ID);
-		verify(statisticsService, never()).getEditionTableData(any(), anyList(), any(), any());
-	}
-
-	@Test
-	@WithUserDetails("username")
-	void getStatisticsTableAllowsIfCanViewEditionStatistics() throws Exception {
-		when(authService.canViewEditionStatistics(anyLong())).thenReturn(true);
-		when(editionService.getEdition(anyLong())).thenReturn(EDITION_VIEW);
-		when(statisticsService.getEditionTableData(any(), anyList(), any(), any()))
-				.thenReturn(Collections.emptyList());
-
-		mockMvc.perform(get("/edition/{id}/statistics-table", EDITION_ID))
-				.andExpect(status().isOk());
-
-		verify(authService, times(2)).canViewEditionStatistics(EDITION_ID);
-		verify(statisticsService).getEditionTableData(any(), anyList(), any(), any());
-	}
-
 	@Test
 	@WithUserDetails("username")
 	void removeMemberVerifiesCanRemoveMember() throws Exception {
@@ -437,8 +422,7 @@ public class EditionControllerTest {
 				.cohort(EDITION_CREATE.getCohort())
 				.course(EDITION_CREATE.getCourse())
 				.startDate(EDITION_CREATE.getStartDate())
-				.endDate(EDITION_CREATE.getEndDate())
-				.enrollability(EDITION_CREATE.getEnrollability()));
+				.endDate(EDITION_CREATE.getEndDate()));
 	}
 
 	@Test
@@ -689,7 +673,6 @@ public class EditionControllerTest {
 				get("/edition/staff/1"),
 				get("/edition/members/1"),
 				get("/edition/1/statistics"),
-				get("/edition/1/statistics-table"),
 				post("/edition/join/1")
 						.contentType(MediaType.APPLICATION_FORM_URLENCODED)
 						.content(EntityUtils.toString(new UrlEncodedFormEntity(List.of(
diff --git a/src/test/java/nl/tudelft/submit/controller/GradeControllerTest.java b/src/test/java/nl/tudelft/submit/controller/GradeControllerTest.java
index 10b266642710193267f10f7d5c272f803e2151e4..0a3028a250276f1645e484d79f1541d67323067b 100644
--- a/src/test/java/nl/tudelft/submit/controller/GradeControllerTest.java
+++ b/src/test/java/nl/tudelft/submit/controller/GradeControllerTest.java
@@ -27,7 +27,6 @@ import java.time.LocalDateTime;
 import java.util.*;
 
 import nl.tudelft.labracore.lib.api.GradeScheme;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.dto.create.grading.GradedFeedbackCreateDTO;
 import nl.tudelft.submit.dto.view.FormulaTestDTO;
 import nl.tudelft.submit.model.SubmitAssignment;
@@ -50,6 +49,8 @@ import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.MvcResult;
 import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
 
+import application.test.TestSubmitApplication;
+
 import com.fasterxml.jackson.databind.ObjectMapper;
 
 @AutoConfigureMockMvc
diff --git a/src/test/java/nl/tudelft/submit/controller/HomeControllerTest.java b/src/test/java/nl/tudelft/submit/controller/HomeControllerTest.java
index 1ffbddd0a16d63193e603714dbfbe67f201e0610..f70e4413b79b934a2a3315d759ae4951e5521ae8 100644
--- a/src/test/java/nl/tudelft/submit/controller/HomeControllerTest.java
+++ b/src/test/java/nl/tudelft/submit/controller/HomeControllerTest.java
@@ -20,8 +20,6 @@ package nl.tudelft.submit.controller;
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
 
-import nl.tudelft.submit.TestSubmitApplication;
-
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
@@ -29,6 +27,8 @@ import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.security.test.context.support.WithUserDetails;
 import org.springframework.test.web.servlet.MockMvc;
 
+import application.test.TestSubmitApplication;
+
 @AutoConfigureMockMvc
 @SpringBootTest(classes = TestSubmitApplication.class)
 class HomeControllerTest {
diff --git a/src/test/java/nl/tudelft/submit/controller/LoginControllerTest.java b/src/test/java/nl/tudelft/submit/controller/LoginControllerTest.java
index a76410cc87dd9c5e60c5005aeb787626aa231c12..8c8ce9881dce1fac91d51274924146c4ae410576 100644
--- a/src/test/java/nl/tudelft/submit/controller/LoginControllerTest.java
+++ b/src/test/java/nl/tudelft/submit/controller/LoginControllerTest.java
@@ -21,14 +21,14 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
 
-import nl.tudelft.submit.TestSubmitApplication;
-
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.test.web.servlet.MockMvc;
 
+import application.test.TestSubmitApplication;
+
 @AutoConfigureMockMvc
 @SpringBootTest(classes = TestSubmitApplication.class)
 class LoginControllerTest {
diff --git a/src/test/java/nl/tudelft/submit/controller/ModuleControllerTest.java b/src/test/java/nl/tudelft/submit/controller/ModuleControllerTest.java
index c2e38b502e84e72104d812ac3ed764c50c734156..97c9f02dbcf676df74d303c87aab163a015dc07c 100644
--- a/src/test/java/nl/tudelft/submit/controller/ModuleControllerTest.java
+++ b/src/test/java/nl/tudelft/submit/controller/ModuleControllerTest.java
@@ -28,7 +28,6 @@ import java.util.Optional;
 
 import nl.tudelft.labracore.api.dto.*;
 import nl.tudelft.labracore.lib.api.GradeScheme;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.dto.create.DivisionsCreateTypeDTO;
 import nl.tudelft.submit.dto.create.ModuleCreateWithDivisionsDTO;
 import nl.tudelft.submit.dto.create.grading.GradingFormulaCreateDTO;
@@ -52,6 +51,8 @@ import org.springframework.security.test.context.support.WithUserDetails;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
 
+import application.test.TestSubmitApplication;
+
 import com.fasterxml.jackson.databind.ObjectMapper;
 
 @AutoConfigureMockMvc
@@ -85,6 +86,9 @@ class ModuleControllerTest {
 			.build();
 	private static final ModuleDetailsDTO MODULE_VIEW = new ModuleDetailsDTO().id(MODULE_ID).name("Module 1")
 			.edition(new EditionSummaryDTO().id(EDITION_ID).name("Test Edition"));
+	private static final SubmitModuleViewDTO SUBMIT_MODULE_VIEW = SubmitModuleViewDTO.builder().id(MODULE_ID)
+			.name("Module 1")
+			.edition(new EditionSummaryDTO().id(EDITION_ID).name("Test Edition")).build();
 
 	@Autowired
 	private MockMvc mockMvc;
@@ -136,7 +140,7 @@ class ModuleControllerTest {
 				.andExpect(model().attribute("module", module))
 				.andExpect(status().isOk());
 
-		verify(authService).canViewModule(MODULE_ID);
+		verify(authService, atLeastOnce()).canViewModule(MODULE_ID);
 		verify(moduleService).getDetailedModuleView(MODULE_ID);
 	}
 
@@ -155,7 +159,7 @@ class ModuleControllerTest {
 	@Test
 	@WithUserDetails("username")
 	void getModuleGroupsAllowsIfCanViewModuleGroups() throws Exception {
-		when(moduleService.getModuleById(anyLong())).thenReturn(MODULE_VIEW);
+		when(moduleService.getDetailedModuleView(anyLong())).thenReturn(SUBMIT_MODULE_VIEW);
 		when(authService.canViewModuleGroups(anyLong())).thenReturn(true);
 		when(editionService.getEdition(anyLong()))
 				.thenReturn(new EditionDetailsDTO().course(new CourseSummaryDTO()));
@@ -200,6 +204,7 @@ class ModuleControllerTest {
 	void getStatisticsAllowsIfCanViewModuleStatistics() throws Exception {
 		when(authService.canViewModuleStatistics(anyLong())).thenReturn(true);
 		when(moduleService.getModuleById(anyLong())).thenReturn(MODULE_VIEW);
+		when(moduleService.getDetailedModuleView(anyLong())).thenReturn(SUBMIT_MODULE_VIEW);
 		when(statisticsService.getModuleStatistics(any()))
 				.thenReturn(ModuleStatisticsDTO.builder().totalGroups(0).totalStudents(0).build());
 		when(editionService.getEdition(anyLong()))
@@ -224,22 +229,6 @@ class ModuleControllerTest {
 		verify(statisticsService, never()).getModuleTableData(any(), any(), any());
 	}
 
-	@Test
-	@WithUserDetails("username")
-	void getStatisticsTableAllowsIfCanViewModuleStatistics() throws Exception {
-		when(authService.canViewModuleStatistics(anyLong())).thenReturn(true);
-		when(moduleService.getModuleById(anyLong())).thenReturn(MODULE_VIEW);
-		when(statisticsService.getModuleTableData(any(), any(), any())).thenReturn(Collections.emptyList());
-		when(editionService.getEdition(anyLong()))
-				.thenReturn(new EditionDetailsDTO().course(new CourseSummaryDTO()));
-
-		mockMvc.perform(get("/module/{id}/statistics-table", MODULE_ID))
-				.andExpect(status().isOk());
-
-		verify(authService, times(2)).canViewModuleStatistics(MODULE_ID);
-		verify(statisticsService).getModuleTableData(any(), any(), any());
-	}
-
 	@Test
 	@WithUserDetails("username")
 	void addModuleVerifiesCanCreateModule() throws Exception {
diff --git a/src/test/java/nl/tudelft/submit/controller/NotificationControllerTest.java b/src/test/java/nl/tudelft/submit/controller/NotificationControllerTest.java
index b4964bcd5e459927f35e464ce6d653710dcc5706..c20deeb98b8f1a90e257860b3b43f607d3a26891 100644
--- a/src/test/java/nl/tudelft/submit/controller/NotificationControllerTest.java
+++ b/src/test/java/nl/tudelft/submit/controller/NotificationControllerTest.java
@@ -25,7 +25,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
 
 import java.util.List;
 
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.dto.view.NotificationViewDTO;
 import nl.tudelft.submit.security.AuthorizationService;
 import nl.tudelft.submit.service.NotificationService;
@@ -42,6 +41,8 @@ import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.MvcResult;
 import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
 
+import application.test.TestSubmitApplication;
+
 import com.fasterxml.jackson.databind.ObjectMapper;
 
 @AutoConfigureMockMvc
diff --git a/src/test/java/nl/tudelft/submit/controller/PersonControllerTest.java b/src/test/java/nl/tudelft/submit/controller/PersonControllerTest.java
index 437c9aafcfb403508fad3085dfa3685b71dceca5..433042dab37e6cf185a073f10777bdac459f16ed 100644
--- a/src/test/java/nl/tudelft/submit/controller/PersonControllerTest.java
+++ b/src/test/java/nl/tudelft/submit/controller/PersonControllerTest.java
@@ -26,7 +26,6 @@ import java.util.List;
 
 import nl.tudelft.labracore.api.dto.PersonDetailsDTO;
 import nl.tudelft.labracore.api.dto.PersonSummaryDTO;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.security.AuthorizationService;
 import nl.tudelft.submit.service.ModuleService;
 import nl.tudelft.submit.service.PersonService;
@@ -43,6 +42,8 @@ import org.springframework.security.test.context.support.WithUserDetails;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
 
+import application.test.TestSubmitApplication;
+
 import com.fasterxml.jackson.databind.ObjectMapper;
 
 @AutoConfigureMockMvc
diff --git a/src/test/java/nl/tudelft/submit/controller/RoleControllerTest.java b/src/test/java/nl/tudelft/submit/controller/RoleControllerTest.java
index 06810bc52b07ef698f9a3973fcb4f3d1bd45a909..213ccd02395af77ed0db8f8bd8c2be5d0217ca9d 100644
--- a/src/test/java/nl/tudelft/submit/controller/RoleControllerTest.java
+++ b/src/test/java/nl/tudelft/submit/controller/RoleControllerTest.java
@@ -24,9 +24,10 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
 
 import java.util.List;
 
+import javax.transaction.Transactional;
+
 import nl.tudelft.labracore.api.dto.Id;
 import nl.tudelft.labracore.api.dto.RolePatchDTO;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.dto.create.note.RoleNoteCreateDTO;
 import nl.tudelft.submit.dto.id.RoleId;
 import nl.tudelft.submit.security.AuthorizationService;
@@ -45,8 +46,11 @@ import org.springframework.security.test.context.support.WithUserDetails;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
 
+import application.test.TestSubmitApplication;
+
 import com.fasterxml.jackson.databind.ObjectMapper;
 
+@Transactional
 @AutoConfigureMockMvc
 @SpringBootTest(classes = TestSubmitApplication.class)
 class RoleControllerTest {
diff --git a/src/test/java/nl/tudelft/submit/controller/ScriptControllerTest.java b/src/test/java/nl/tudelft/submit/controller/ScriptControllerTest.java
index aa405aca3ad91fc0107bcf6afd4947b76a20a12b..6e62760032cae04a3736feb74a957043abdd16c6 100644
--- a/src/test/java/nl/tudelft/submit/controller/ScriptControllerTest.java
+++ b/src/test/java/nl/tudelft/submit/controller/ScriptControllerTest.java
@@ -29,7 +29,6 @@ import java.util.List;
 
 import nl.tudelft.labracore.api.dto.*;
 import nl.tudelft.librador.dto.view.View;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.dto.create.script.ScriptCreateDTO;
 import nl.tudelft.submit.dto.create.script.ScriptFeedbackReceiveDTO;
 import nl.tudelft.submit.dto.create.script.ScriptWagonCreateDTO;
@@ -61,6 +60,8 @@ import org.springframework.security.test.context.support.WithUserDetails;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
 
+import application.test.TestSubmitApplication;
+
 import com.fasterxml.jackson.databind.ObjectMapper;
 
 @AutoConfigureMockMvc
diff --git a/src/test/java/nl/tudelft/submit/controller/StudentGroupControllerTest.java b/src/test/java/nl/tudelft/submit/controller/StudentGroupControllerTest.java
index 2295edf6f0b0ef0249eb14e1d267259a6463c8cb..943b98a439b5ce354c055ad7d7b629aac9d9f94b 100644
--- a/src/test/java/nl/tudelft/submit/controller/StudentGroupControllerTest.java
+++ b/src/test/java/nl/tudelft/submit/controller/StudentGroupControllerTest.java
@@ -28,7 +28,6 @@ import java.util.*;
 import javax.transaction.Transactional;
 
 import nl.tudelft.labracore.api.dto.*;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.dto.create.note.GroupNoteCreateDTO;
 import nl.tudelft.submit.dto.id.GroupId;
 import nl.tudelft.submit.dto.view.labracore.SubmitGroupDetailsDTO;
@@ -49,6 +48,8 @@ import org.springframework.security.test.context.support.WithUserDetails;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
 
+import application.test.TestSubmitApplication;
+
 import com.fasterxml.jackson.databind.ObjectMapper;
 
 @Transactional
@@ -136,7 +137,6 @@ class StudentGroupControllerTest {
 				.andExpect(status().isOk());
 
 		verify(authService).canViewGroup(GROUP_ID);
-		verify(authService, times(5)).isStudentInModule(MODULE_ID);
 		verify(groupService).getSubmitGroupDetails(GROUP_ID);
 		verify(groupService).getModuleIdFromGroup(GROUP_ID);
 		verify(assignmentService).getClosestDeadlineAssignmentForModule(MODULE_ID);
@@ -245,6 +245,9 @@ class StudentGroupControllerTest {
 	void joinGroupAllowsIfCanJoinGroup() throws Exception {
 		when(authService.canJoinGroup(anyLong())).thenReturn(true);
 		when(groupService.addPersonToGroup(anyLong(), anyLong())).thenReturn(-1L);
+		when(groupService.getModuleIdFromGroup(anyLong())).thenReturn(MODULE_ID);
+		when(moduleService.getModuleById(anyLong()))
+				.thenReturn(new ModuleDetailsDTO().edition(new EditionSummaryDTO().id(1L)).id(MODULE_ID));
 
 		mockMvc.perform(post("/group/{id}/join", GROUP_ID).with(csrf()))
 				.andExpect(status().is3xxRedirection());
diff --git a/src/test/java/nl/tudelft/submit/controller/SubmissionControllerTest.java b/src/test/java/nl/tudelft/submit/controller/SubmissionControllerTest.java
index ada42731be334eda4d86fe38571351ab9150e353..665f6ebf646681902055d11d347fab6ed9bcc04a 100644
--- a/src/test/java/nl/tudelft/submit/controller/SubmissionControllerTest.java
+++ b/src/test/java/nl/tudelft/submit/controller/SubmissionControllerTest.java
@@ -31,7 +31,6 @@ import java.time.LocalDateTime;
 import java.util.*;
 
 import nl.tudelft.labracore.api.dto.*;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.dto.create.MailMessageCreateDTO;
 import nl.tudelft.submit.dto.create.SubmissionDownloadConfigCreateDTO;
 import nl.tudelft.submit.dto.create.note.SubmissionNoteCreateDTO;
@@ -60,6 +59,8 @@ import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
 import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
 
+import application.test.TestSubmitApplication;
+
 import com.fasterxml.jackson.databind.ObjectMapper;
 
 @WebAppConfiguration
@@ -200,7 +201,7 @@ class SubmissionControllerTest {
 				.andExpect(status().isForbidden());
 
 		verify(authService).canSubmitAssignment(anyLong(), anyLong());
-		verifyZeroInteractions(submissionService);
+		verifyNoInteractions(submissionService);
 	}
 
 	@Test
@@ -299,7 +300,7 @@ class SubmissionControllerTest {
 				.andExpect(status().isForbidden());
 
 		verify(authService).canDownloadSubmission(SUBMISSION_ID);
-		verifyZeroInteractions(fileService);
+		verifyNoInteractions(fileService);
 	}
 
 	@Test
diff --git a/src/test/java/nl/tudelft/submit/controller/UserSettingsControllerTest.java b/src/test/java/nl/tudelft/submit/controller/UserSettingsControllerTest.java
index a9defa824ecc3acb95bddd1eeddf9c6444a7b84b..14f7443017f27335f157a5d6c80a31121138d5b8 100644
--- a/src/test/java/nl/tudelft/submit/controller/UserSettingsControllerTest.java
+++ b/src/test/java/nl/tudelft/submit/controller/UserSettingsControllerTest.java
@@ -25,7 +25,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
 import java.util.List;
 
 import nl.tudelft.labracore.api.dto.*;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.dto.patch.UserSettingsPatchDTO;
 import nl.tudelft.submit.model.UserSettings;
 import nl.tudelft.submit.service.UserSettingsService;
@@ -43,6 +42,8 @@ import org.springframework.security.test.context.support.WithUserDetails;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
 
+import application.test.TestSubmitApplication;
+
 import com.fasterxml.jackson.databind.ObjectMapper;
 
 @AutoConfigureMockMvc
diff --git a/src/test/java/nl/tudelft/submit/controller/VersionControllerTest.java b/src/test/java/nl/tudelft/submit/controller/VersionControllerTest.java
index 80c1ba18fd5f0a06d916d96d4d29a839e5b578ea..a314ee98bc6826f6921278d3297db35d26344a65 100644
--- a/src/test/java/nl/tudelft/submit/controller/VersionControllerTest.java
+++ b/src/test/java/nl/tudelft/submit/controller/VersionControllerTest.java
@@ -27,7 +27,6 @@ import java.util.List;
 import java.util.Map;
 
 import nl.tudelft.labracore.api.dto.*;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.dto.create.VersionCreateDTO;
 import nl.tudelft.submit.dto.id.SubmitGroupIdDTO;
 import nl.tudelft.submit.dto.patch.VersionPatchDTO;
@@ -50,6 +49,8 @@ import org.springframework.security.test.context.support.WithUserDetails;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
 
+import application.test.TestSubmitApplication;
+
 import com.fasterxml.jackson.databind.ObjectMapper;
 
 @AutoConfigureMockMvc
diff --git a/src/test/java/nl/tudelft/submit/csv/CSVServiceTest.java b/src/test/java/nl/tudelft/submit/csv/CSVServiceTest.java
index d3008382cb3c86b2fdbc759982f1134b5aa75cfb..ef55aae1fa439f9241d36f7798b4dcdd2d857fcb 100644
--- a/src/test/java/nl/tudelft/submit/csv/CSVServiceTest.java
+++ b/src/test/java/nl/tudelft/submit/csv/CSVServiceTest.java
@@ -28,14 +28,14 @@ import java.util.List;
 
 import javax.transaction.Transactional;
 
-import nl.tudelft.submit.TestSubmitApplication;
-
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 
+import application.test.TestSubmitApplication;
+
 @Transactional
 @SpringBootTest(classes = TestSubmitApplication.class)
 class CSVServiceTest {
diff --git a/src/test/java/nl/tudelft/submit/dto/AssignmentNoteDTOTest.java b/src/test/java/nl/tudelft/submit/dto/AssignmentNoteDTOTest.java
index 2451decce0107172432558c6fea9f09e7aa3e72c..2516af8fca0b31cfc4add3653f334b907b97b50c 100644
--- a/src/test/java/nl/tudelft/submit/dto/AssignmentNoteDTOTest.java
+++ b/src/test/java/nl/tudelft/submit/dto/AssignmentNoteDTOTest.java
@@ -22,7 +22,6 @@ import static org.assertj.core.api.Assertions.assertThat;
 import java.time.LocalDateTime;
 
 import nl.tudelft.labracore.api.dto.AssignmentIdDTO;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.dto.create.note.AssignmentNoteCreateDTO;
 import nl.tudelft.submit.dto.id.AssignmentId;
 import nl.tudelft.submit.dto.patch.note.AssignmentNotePatchDTO;
@@ -32,6 +31,8 @@ import nl.tudelft.submit.model.note.AssignmentNote;
 import org.junit.jupiter.api.Test;
 import org.springframework.boot.test.context.SpringBootTest;
 
+import application.test.TestSubmitApplication;
+
 @SpringBootTest(classes = TestSubmitApplication.class)
 class AssignmentNoteDTOTest {
 
diff --git a/src/test/java/nl/tudelft/submit/dto/GroupNoteDTOTest.java b/src/test/java/nl/tudelft/submit/dto/GroupNoteDTOTest.java
index c94f789ab0c1b4d29b6928fd61b7850021ba7883..85ee367b7e4100617f0afb06b8a435b1e12c0fba 100644
--- a/src/test/java/nl/tudelft/submit/dto/GroupNoteDTOTest.java
+++ b/src/test/java/nl/tudelft/submit/dto/GroupNoteDTOTest.java
@@ -22,7 +22,6 @@ import static org.assertj.core.api.Assertions.assertThat;
 import java.time.LocalDateTime;
 
 import nl.tudelft.labracore.api.dto.StudentGroupIdDTO;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.dto.create.note.GroupNoteCreateDTO;
 import nl.tudelft.submit.dto.id.GroupId;
 import nl.tudelft.submit.dto.patch.note.GroupNotePatchDTO;
@@ -32,6 +31,8 @@ import nl.tudelft.submit.model.note.GroupNote;
 import org.junit.jupiter.api.Test;
 import org.springframework.boot.test.context.SpringBootTest;
 
+import application.test.TestSubmitApplication;
+
 @SpringBootTest(classes = TestSubmitApplication.class)
 class GroupNoteDTOTest {
 
diff --git a/src/test/java/nl/tudelft/submit/dto/RoleNoteDTOTest.java b/src/test/java/nl/tudelft/submit/dto/RoleNoteDTOTest.java
index e2713f4e0da703a59cdb3838380e399d19d6b850..eb7c300d24ed8da846445f100b84a8cd6ea33926 100644
--- a/src/test/java/nl/tudelft/submit/dto/RoleNoteDTOTest.java
+++ b/src/test/java/nl/tudelft/submit/dto/RoleNoteDTOTest.java
@@ -23,7 +23,6 @@ import java.time.LocalDateTime;
 
 import nl.tudelft.labracore.api.dto.Id;
 import nl.tudelft.labracore.api.dto.RoleIdDTO;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.dto.create.note.RoleNoteCreateDTO;
 import nl.tudelft.submit.dto.id.RoleId;
 import nl.tudelft.submit.dto.patch.note.RoleNotePatchDTO;
@@ -33,6 +32,8 @@ import nl.tudelft.submit.model.note.RoleNote;
 import org.junit.jupiter.api.Test;
 import org.springframework.boot.test.context.SpringBootTest;
 
+import application.test.TestSubmitApplication;
+
 @SpringBootTest(classes = TestSubmitApplication.class)
 class RoleNoteDTOTest {
 
diff --git a/src/test/java/nl/tudelft/submit/dto/ScriptFeedbackDTOTest.java b/src/test/java/nl/tudelft/submit/dto/ScriptFeedbackDTOTest.java
index 3715c9ec07ec3e9ca4f0454ecf729f7051bf0e94..9e1b4f58411c77584a08c1a881befab7f3dda040 100644
--- a/src/test/java/nl/tudelft/submit/dto/ScriptFeedbackDTOTest.java
+++ b/src/test/java/nl/tudelft/submit/dto/ScriptFeedbackDTOTest.java
@@ -27,7 +27,6 @@ import javax.transaction.Transactional;
 
 import nl.tudelft.labracore.api.dto.RoleSummaryDTO;
 import nl.tudelft.labracore.lib.api.GradeScheme;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.dto.create.FeedbackCreateDTO;
 import nl.tudelft.submit.model.Feedback;
 import nl.tudelft.submit.model.script.Script;
@@ -38,6 +37,8 @@ import org.junit.jupiter.api.Test;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.boot.test.mock.mockito.MockBean;
 
+import application.test.TestSubmitApplication;
+
 @Transactional
 @SpringBootTest(classes = TestSubmitApplication.class)
 class ScriptFeedbackDTOTest {
diff --git a/src/test/java/nl/tudelft/submit/dto/SubmissionNoteDTOTest.java b/src/test/java/nl/tudelft/submit/dto/SubmissionNoteDTOTest.java
index f208b10dedfd775741951264eeee5621afa3dee8..32caf43947d01710922e6de60cc32e43f5547052 100644
--- a/src/test/java/nl/tudelft/submit/dto/SubmissionNoteDTOTest.java
+++ b/src/test/java/nl/tudelft/submit/dto/SubmissionNoteDTOTest.java
@@ -22,7 +22,6 @@ import static org.assertj.core.api.Assertions.assertThat;
 import java.time.LocalDateTime;
 
 import nl.tudelft.labracore.api.dto.SubmissionIdDTO;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.dto.create.note.SubmissionNoteCreateDTO;
 import nl.tudelft.submit.dto.id.SubmissionId;
 import nl.tudelft.submit.dto.patch.note.SubmissionNotePatchDTO;
@@ -32,6 +31,8 @@ import nl.tudelft.submit.model.note.SubmissionNote;
 import org.junit.jupiter.api.Test;
 import org.springframework.boot.test.context.SpringBootTest;
 
+import application.test.TestSubmitApplication;
+
 @SpringBootTest(classes = TestSubmitApplication.class)
 class SubmissionNoteDTOTest {
 
diff --git a/src/test/java/nl/tudelft/submit/dto/VersionDTOTest.java b/src/test/java/nl/tudelft/submit/dto/VersionDTOTest.java
index 682e0ccdd37a91fe2c8178958852069940f087c8..3ae8c2f1b4b56c1b2326e303d05ee94c4294b72e 100644
--- a/src/test/java/nl/tudelft/submit/dto/VersionDTOTest.java
+++ b/src/test/java/nl/tudelft/submit/dto/VersionDTOTest.java
@@ -24,7 +24,6 @@ import java.util.List;
 import java.util.Map;
 
 import nl.tudelft.labracore.api.dto.AssignmentIdDTO;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.dto.create.VersionCreateDTO;
 import nl.tudelft.submit.dto.patch.VersionPatchDTO;
 import nl.tudelft.submit.model.SubmitGroup;
@@ -33,6 +32,8 @@ import nl.tudelft.submit.model.Version;
 import org.junit.jupiter.api.Test;
 import org.springframework.boot.test.context.SpringBootTest;
 
+import application.test.TestSubmitApplication;
+
 @SpringBootTest(classes = TestSubmitApplication.class)
 public class VersionDTOTest {
 
diff --git a/src/test/java/nl/tudelft/submit/repository/FeedbackRepositoryTest.java b/src/test/java/nl/tudelft/submit/repository/FeedbackRepositoryTest.java
index a61ac226ae8512dcc1be16f588883c1f73e1d612..56ebf3774f7130efff2f59c71de2f43dea039a28 100644
--- a/src/test/java/nl/tudelft/submit/repository/FeedbackRepositoryTest.java
+++ b/src/test/java/nl/tudelft/submit/repository/FeedbackRepositoryTest.java
@@ -27,7 +27,6 @@ import java.util.Random;
 import javax.transaction.Transactional;
 
 import nl.tudelft.labracore.api.dto.RoleSummaryDTO;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.dto.create.FeedbackCreateDTO;
 import nl.tudelft.submit.model.Feedback;
 
@@ -36,11 +35,11 @@ import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.data.rest.webmvc.ResourceNotFoundException;
-import org.springframework.test.annotation.DirtiesContext;
+
+import application.test.TestSubmitApplication;
 
 @Transactional
 @SpringBootTest(classes = TestSubmitApplication.class)
-@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
 class FeedbackRepositoryTest {
 
 	private final Long SUBMISSION_ID = 309L;
diff --git a/src/test/java/nl/tudelft/submit/repository/NotificationRepositoryTest.java b/src/test/java/nl/tudelft/submit/repository/NotificationRepositoryTest.java
index d3954abc1cdb9d2c0aeb49216350c506a5adc55f..bec787ddab3e323017f672ddc95f818075bf2bd1 100644
--- a/src/test/java/nl/tudelft/submit/repository/NotificationRepositoryTest.java
+++ b/src/test/java/nl/tudelft/submit/repository/NotificationRepositoryTest.java
@@ -24,7 +24,6 @@ import java.util.List;
 
 import javax.transaction.Transactional;
 
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.model.Notification;
 
 import org.junit.jupiter.api.BeforeEach;
@@ -32,6 +31,8 @@ import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 
+import application.test.TestSubmitApplication;
+
 @Transactional
 @SpringBootTest(classes = TestSubmitApplication.class)
 public class NotificationRepositoryTest {
diff --git a/src/test/java/nl/tudelft/submit/repository/SubmissionKeyRepositoryTest.java b/src/test/java/nl/tudelft/submit/repository/SubmissionKeyRepositoryTest.java
index 1c8c3f86f915fc3bb765953628abd9c4c6560816..f0982595bf7b10ceadb1465b91fee93ef5ba2d63 100644
--- a/src/test/java/nl/tudelft/submit/repository/SubmissionKeyRepositoryTest.java
+++ b/src/test/java/nl/tudelft/submit/repository/SubmissionKeyRepositoryTest.java
@@ -24,7 +24,6 @@ import java.time.LocalDateTime;
 
 import javax.transaction.Transactional;
 
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.model.script.SubmissionKey;
 
 import org.junit.jupiter.api.BeforeEach;
@@ -33,6 +32,8 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.data.rest.webmvc.ResourceNotFoundException;
 
+import application.test.TestSubmitApplication;
+
 @Transactional
 @SpringBootTest(classes = TestSubmitApplication.class)
 class SubmissionKeyRepositoryTest {
diff --git a/src/test/java/nl/tudelft/submit/repository/SubmitGroupRepositoryTest.java b/src/test/java/nl/tudelft/submit/repository/SubmitGroupRepositoryTest.java
index 39cf06f035f5fafeb0db51d8f9967c619313334a..3596669c5969fff255270609673e1586db932d35 100644
--- a/src/test/java/nl/tudelft/submit/repository/SubmitGroupRepositoryTest.java
+++ b/src/test/java/nl/tudelft/submit/repository/SubmitGroupRepositoryTest.java
@@ -25,7 +25,6 @@ import java.util.List;
 
 import javax.transaction.Transactional;
 
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.model.SubmitGroup;
 import nl.tudelft.submit.model.Version;
 
@@ -35,6 +34,8 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.data.rest.webmvc.ResourceNotFoundException;
 
+import application.test.TestSubmitApplication;
+
 @Transactional
 @SpringBootTest(classes = TestSubmitApplication.class)
 class SubmitGroupRepositoryTest {
diff --git a/src/test/java/nl/tudelft/submit/repository/SwitchEventRepositoryTest.java b/src/test/java/nl/tudelft/submit/repository/SwitchEventRepositoryTest.java
index 6b97fd945916158afbaf8b0ad12142ff0edecb66..4557a87b3eb4c77acf25203aa11cda1b6a47fbe2 100644
--- a/src/test/java/nl/tudelft/submit/repository/SwitchEventRepositoryTest.java
+++ b/src/test/java/nl/tudelft/submit/repository/SwitchEventRepositoryTest.java
@@ -27,7 +27,6 @@ import java.util.List;
 
 import javax.transaction.Transactional;
 
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.model.SwitchEvent;
 
 import org.junit.jupiter.api.BeforeEach;
@@ -36,6 +35,8 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.data.rest.webmvc.ResourceNotFoundException;
 
+import application.test.TestSubmitApplication;
+
 @Transactional
 @SpringBootTest(classes = TestSubmitApplication.class)
 class SwitchEventRepositoryTest {
diff --git a/src/test/java/nl/tudelft/submit/repository/UserSettingsRepositoryTest.java b/src/test/java/nl/tudelft/submit/repository/UserSettingsRepositoryTest.java
index 246c3cc24db872b9cc051dbbbfd4aca988d21828..b03dd974b6d3d29670bda8e3d09987556844e242 100644
--- a/src/test/java/nl/tudelft/submit/repository/UserSettingsRepositoryTest.java
+++ b/src/test/java/nl/tudelft/submit/repository/UserSettingsRepositoryTest.java
@@ -23,7 +23,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 import javax.transaction.Transactional;
 
 import nl.tudelft.labracore.api.dto.PersonIdDTO;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.model.UserSettings;
 
 import org.junit.jupiter.api.BeforeEach;
@@ -32,6 +31,8 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.data.rest.webmvc.ResourceNotFoundException;
 
+import application.test.TestSubmitApplication;
+
 @Transactional
 @SpringBootTest(classes = TestSubmitApplication.class)
 class UserSettingsRepositoryTest {
diff --git a/src/test/java/nl/tudelft/submit/repository/VersionRepositoryTest.java b/src/test/java/nl/tudelft/submit/repository/VersionRepositoryTest.java
index ffc129781eddf96faaeb38cf75c1af9ba4f185a7..80f7665191677a1c634616e35a8eb903f94b7c7f 100644
--- a/src/test/java/nl/tudelft/submit/repository/VersionRepositoryTest.java
+++ b/src/test/java/nl/tudelft/submit/repository/VersionRepositoryTest.java
@@ -26,7 +26,6 @@ import java.util.Optional;
 
 import javax.transaction.Transactional;
 
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.model.SubmitGroup;
 import nl.tudelft.submit.model.Version;
 
@@ -36,6 +35,8 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.data.rest.webmvc.ResourceNotFoundException;
 
+import application.test.TestSubmitApplication;
+
 @Transactional
 @SpringBootTest(classes = TestSubmitApplication.class)
 public class VersionRepositoryTest {
diff --git a/src/test/java/nl/tudelft/submit/repository/grading/EditionCalculatedScoreRepositoryTest.java b/src/test/java/nl/tudelft/submit/repository/grading/EditionCalculatedScoreRepositoryTest.java
index 204c5e382949152c0359fa41e14c4e80088cf237..d70f80ebfbd0ec04854d1d89974257d36257779c 100644
--- a/src/test/java/nl/tudelft/submit/repository/grading/EditionCalculatedScoreRepositoryTest.java
+++ b/src/test/java/nl/tudelft/submit/repository/grading/EditionCalculatedScoreRepositoryTest.java
@@ -23,7 +23,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 import javax.transaction.Transactional;
 
 import nl.tudelft.labracore.lib.api.GradeScheme;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.model.grading.CalculatedScore;
 import nl.tudelft.submit.model.grading.EditionCalculatedScore;
 
@@ -33,6 +32,8 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.data.rest.webmvc.ResourceNotFoundException;
 
+import application.test.TestSubmitApplication;
+
 @Transactional
 @SpringBootTest(classes = TestSubmitApplication.class)
 class EditionCalculatedScoreRepositoryTest {
diff --git a/src/test/java/nl/tudelft/submit/repository/grading/ModuleCalculatedScoreRepositoryTest.java b/src/test/java/nl/tudelft/submit/repository/grading/ModuleCalculatedScoreRepositoryTest.java
index 58f6aa53c7b920eca7671ab895bee6f79bbf10d9..1f1182d94adc5fe04f505a916ef4f66f86cbc9d5 100644
--- a/src/test/java/nl/tudelft/submit/repository/grading/ModuleCalculatedScoreRepositoryTest.java
+++ b/src/test/java/nl/tudelft/submit/repository/grading/ModuleCalculatedScoreRepositoryTest.java
@@ -23,7 +23,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 import javax.transaction.Transactional;
 
 import nl.tudelft.labracore.lib.api.GradeScheme;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.model.grading.CalculatedScore;
 import nl.tudelft.submit.model.grading.ModuleCalculatedScore;
 
@@ -33,6 +32,8 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.data.rest.webmvc.ResourceNotFoundException;
 
+import application.test.TestSubmitApplication;
+
 @Transactional
 @SpringBootTest(classes = TestSubmitApplication.class)
 class ModuleCalculatedScoreRepositoryTest {
diff --git a/src/test/java/nl/tudelft/submit/repository/note/AssignmentNoteRepositoryTest.java b/src/test/java/nl/tudelft/submit/repository/note/AssignmentNoteRepositoryTest.java
index ebea0947a96e3627627933fcb715100a085e9efa..8902cb53d4b081e8c14eb93004b0c7452bd6d72a 100644
--- a/src/test/java/nl/tudelft/submit/repository/note/AssignmentNoteRepositoryTest.java
+++ b/src/test/java/nl/tudelft/submit/repository/note/AssignmentNoteRepositoryTest.java
@@ -25,7 +25,6 @@ import java.util.List;
 
 import javax.transaction.Transactional;
 
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.dto.id.AssignmentId;
 import nl.tudelft.submit.model.Signature;
 import nl.tudelft.submit.model.note.AssignmentNote;
@@ -36,6 +35,8 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.data.rest.webmvc.ResourceNotFoundException;
 
+import application.test.TestSubmitApplication;
+
 @Transactional
 @SpringBootTest(classes = TestSubmitApplication.class)
 class AssignmentNoteRepositoryTest {
diff --git a/src/test/java/nl/tudelft/submit/repository/note/GroupNoteRepositoryTest.java b/src/test/java/nl/tudelft/submit/repository/note/GroupNoteRepositoryTest.java
index 6697fbaa1816e576b8bc3fd28d95754238efba0a..7882cf1809fa208b07d7afd971d8a91cdea0a627 100644
--- a/src/test/java/nl/tudelft/submit/repository/note/GroupNoteRepositoryTest.java
+++ b/src/test/java/nl/tudelft/submit/repository/note/GroupNoteRepositoryTest.java
@@ -25,7 +25,6 @@ import java.util.List;
 
 import javax.transaction.Transactional;
 
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.dto.id.GroupId;
 import nl.tudelft.submit.model.Signature;
 import nl.tudelft.submit.model.note.GroupNote;
@@ -36,6 +35,8 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.data.rest.webmvc.ResourceNotFoundException;
 
+import application.test.TestSubmitApplication;
+
 @Transactional
 @SpringBootTest(classes = TestSubmitApplication.class)
 class GroupNoteRepositoryTest {
diff --git a/src/test/java/nl/tudelft/submit/repository/note/RoleNoteRepositoryTest.java b/src/test/java/nl/tudelft/submit/repository/note/RoleNoteRepositoryTest.java
index f96022ef83fbda44e542f8d692e070181db42817..8f620f698cb739ed67f5e47d6ff40acaf4a39f61 100644
--- a/src/test/java/nl/tudelft/submit/repository/note/RoleNoteRepositoryTest.java
+++ b/src/test/java/nl/tudelft/submit/repository/note/RoleNoteRepositoryTest.java
@@ -26,7 +26,6 @@ import java.util.List;
 import javax.transaction.Transactional;
 
 import nl.tudelft.labracore.api.dto.Id;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.dto.id.RoleId;
 import nl.tudelft.submit.model.Signature;
 import nl.tudelft.submit.model.note.RoleNote;
@@ -37,6 +36,8 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.data.rest.webmvc.ResourceNotFoundException;
 
+import application.test.TestSubmitApplication;
+
 @Transactional
 @SpringBootTest(classes = TestSubmitApplication.class)
 class RoleNoteRepositoryTest {
diff --git a/src/test/java/nl/tudelft/submit/repository/note/SubmissionNoteRepositoryTest.java b/src/test/java/nl/tudelft/submit/repository/note/SubmissionNoteRepositoryTest.java
index bdadff11f01791408e6ecd047f948ffb5e096043..6aec162f609a93613ed7030fca00301252717ece 100644
--- a/src/test/java/nl/tudelft/submit/repository/note/SubmissionNoteRepositoryTest.java
+++ b/src/test/java/nl/tudelft/submit/repository/note/SubmissionNoteRepositoryTest.java
@@ -25,7 +25,6 @@ import java.util.List;
 
 import javax.transaction.Transactional;
 
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.dto.id.SubmissionId;
 import nl.tudelft.submit.model.Signature;
 import nl.tudelft.submit.model.note.SubmissionNote;
@@ -36,6 +35,8 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.data.rest.webmvc.ResourceNotFoundException;
 
+import application.test.TestSubmitApplication;
+
 @Transactional
 @SpringBootTest(classes = TestSubmitApplication.class)
 class SubmissionNoteRepositoryTest {
diff --git a/src/test/java/nl/tudelft/submit/repository/script/ScriptRepositoryTest.java b/src/test/java/nl/tudelft/submit/repository/script/ScriptRepositoryTest.java
index 60b104df741717e4e6743f61c38c26b4e6e370f9..619cc37ce947bfe5538f41026b6d4c92aa457e71 100644
--- a/src/test/java/nl/tudelft/submit/repository/script/ScriptRepositoryTest.java
+++ b/src/test/java/nl/tudelft/submit/repository/script/ScriptRepositoryTest.java
@@ -21,7 +21,6 @@ import static org.assertj.core.api.Assertions.assertThat;
 
 import javax.transaction.Transactional;
 
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.dto.create.script.ScriptCreateDTO;
 import nl.tudelft.submit.dto.id.ScriptWagonIdDTO;
 import nl.tudelft.submit.dto.patch.script.ScriptPatchDTO;
@@ -34,11 +33,11 @@ import org.modelmapper.ModelMapper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.data.rest.webmvc.ResourceNotFoundException;
-import org.springframework.test.annotation.DirtiesContext;
+
+import application.test.TestSubmitApplication;
 
 @Transactional
 @SpringBootTest(classes = TestSubmitApplication.class)
-@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
 class ScriptRepositoryTest {
 
 	// region Class attributes
diff --git a/src/test/java/nl/tudelft/submit/repository/script/ScriptTrainRepositoryTest.java b/src/test/java/nl/tudelft/submit/repository/script/ScriptTrainRepositoryTest.java
index 4336d8facd19584162a7b303f5aaac8d31d3bc41..2c0563ba50f1d143894fe475cd4ba34ca701340b 100644
--- a/src/test/java/nl/tudelft/submit/repository/script/ScriptTrainRepositoryTest.java
+++ b/src/test/java/nl/tudelft/submit/repository/script/ScriptTrainRepositoryTest.java
@@ -21,7 +21,6 @@ import static org.assertj.core.api.Assertions.assertThat;
 
 import javax.transaction.Transactional;
 
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.dto.create.script.ScriptTrainCreateDTO;
 import nl.tudelft.submit.dto.id.VersionIdDTO;
 import nl.tudelft.submit.model.Version;
@@ -33,11 +32,11 @@ import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.data.rest.webmvc.ResourceNotFoundException;
-import org.springframework.test.annotation.DirtiesContext;
+
+import application.test.TestSubmitApplication;
 
 @Transactional
 @SpringBootTest(classes = TestSubmitApplication.class)
-@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
 class ScriptTrainRepositoryTest {
 
 	// region Class attributes
diff --git a/src/test/java/nl/tudelft/submit/repository/script/ScriptWagonRepositoryTest.java b/src/test/java/nl/tudelft/submit/repository/script/ScriptWagonRepositoryTest.java
index a32867d75f440507868171c5e81112a61b3e145d..d13296eef57bcabf8169383f23595de6ff4979dc 100644
--- a/src/test/java/nl/tudelft/submit/repository/script/ScriptWagonRepositoryTest.java
+++ b/src/test/java/nl/tudelft/submit/repository/script/ScriptWagonRepositoryTest.java
@@ -21,7 +21,6 @@ import static org.assertj.core.api.Assertions.assertThat;
 
 import javax.transaction.Transactional;
 
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.dto.create.script.ScriptWagonCreateDTO;
 import nl.tudelft.submit.dto.id.ScriptTrainIdDTO;
 import nl.tudelft.submit.model.script.ScriptTrain;
@@ -32,11 +31,11 @@ import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.data.rest.webmvc.ResourceNotFoundException;
-import org.springframework.test.annotation.DirtiesContext;
+
+import application.test.TestSubmitApplication;
 
 @Transactional
 @SpringBootTest(classes = TestSubmitApplication.class)
-@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
 class ScriptWagonRepositoryTest {
 
 	// region Class attributes
diff --git a/src/test/java/nl/tudelft/submit/security/AuthorizationServiceTest.java b/src/test/java/nl/tudelft/submit/security/AuthorizationServiceTest.java
index aaa4e922937861b44104038f80ee5c677c2ec192..1d2f6ae82c600cdc23acc0500170889163eab821 100644
--- a/src/test/java/nl/tudelft/submit/security/AuthorizationServiceTest.java
+++ b/src/test/java/nl/tudelft/submit/security/AuthorizationServiceTest.java
@@ -30,7 +30,6 @@ import java.util.Optional;
 import nl.tudelft.labracore.api.AuthorizationControllerApi;
 import nl.tudelft.labracore.api.CourseControllerApi;
 import nl.tudelft.labracore.api.dto.*;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.cache.*;
 import nl.tudelft.submit.dto.id.ScriptTrainIdDTO;
 import nl.tudelft.submit.dto.view.FeedbackViewDTO;
@@ -53,6 +52,7 @@ import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.security.test.context.support.WithUserDetails;
 
 import reactor.core.publisher.Mono;
+import application.test.TestSubmitApplication;
 
 @SpringBootTest(classes = TestSubmitApplication.class)
 class AuthorizationServiceTest {
diff --git a/src/test/java/nl/tudelft/submit/service/AnnouncementServiceTest.java b/src/test/java/nl/tudelft/submit/service/AnnouncementServiceTest.java
index 814ceb92d427736a193e3d06bd2727332ec3f0f2..fb67323b08d9fa5383af832da761ff83cd91b020 100644
--- a/src/test/java/nl/tudelft/submit/service/AnnouncementServiceTest.java
+++ b/src/test/java/nl/tudelft/submit/service/AnnouncementServiceTest.java
@@ -25,7 +25,6 @@ import java.time.LocalTime;
 
 import javax.transaction.Transactional;
 
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.dto.create.announcement.GlobalAnnouncementCreateDTO;
 import nl.tudelft.submit.model.announcement.EditionAnnouncement;
 import nl.tudelft.submit.model.announcement.GlobalAnnouncement;
@@ -36,7 +35,8 @@ import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.test.annotation.DirtiesContext;
+
+import application.test.TestSubmitApplication;
 
 @Transactional
 @SpringBootTest(classes = TestSubmitApplication.class)
@@ -147,7 +147,6 @@ public class AnnouncementServiceTest {
 	}
 
 	@Test
-	@DirtiesContext(methodMode = DirtiesContext.MethodMode.AFTER_METHOD)
 	void addAnnouncement() {
 		Long id = announcementService.addAnnouncement(GlobalAnnouncementCreateDTO.builder()
 				.backgroundColour("#000000")
diff --git a/src/test/java/nl/tudelft/submit/service/ApiKeyServiceTest.java b/src/test/java/nl/tudelft/submit/service/ApiKeyServiceTest.java
index 55738dcc59962e0fdc6e52bfcca45ef841bbb091..9d726a03515ca607d02a23fb82ce56d962ec8845 100644
--- a/src/test/java/nl/tudelft/submit/service/ApiKeyServiceTest.java
+++ b/src/test/java/nl/tudelft/submit/service/ApiKeyServiceTest.java
@@ -19,7 +19,6 @@ package nl.tudelft.submit.service;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.model.api.EditionApiKey;
 import nl.tudelft.submit.repository.EditionApiKeyRepository;
 
@@ -28,6 +27,8 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.transaction.annotation.Transactional;
 
+import application.test.TestSubmitApplication;
+
 @Transactional
 @SpringBootTest(classes = TestSubmitApplication.class)
 public class ApiKeyServiceTest {
diff --git a/src/test/java/nl/tudelft/submit/service/AssignmentServiceTest.java b/src/test/java/nl/tudelft/submit/service/AssignmentServiceTest.java
index d1b855b294f502e2ff588f5e3abd6b9de29d5a09..af84af4fbd850799fb04c6075d6b6a2ddff27727 100644
--- a/src/test/java/nl/tudelft/submit/service/AssignmentServiceTest.java
+++ b/src/test/java/nl/tudelft/submit/service/AssignmentServiceTest.java
@@ -34,7 +34,6 @@ import nl.tudelft.labracore.api.AssignmentControllerApi;
 import nl.tudelft.labracore.api.GradeControllerApi;
 import nl.tudelft.labracore.api.dto.*;
 import nl.tudelft.labracore.lib.api.GradeScheme;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.cache.AssignmentCacheManager;
 import nl.tudelft.submit.csv.CSVService;
 import nl.tudelft.submit.dto.create.SubmitAssignmentCreateDTO;
@@ -59,6 +58,7 @@ import org.springframework.boot.test.mock.mockito.MockBean;
 
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
+import application.test.TestSubmitApplication;
 
 import com.opencsv.exceptions.CsvValidationException;
 
diff --git a/src/test/java/nl/tudelft/submit/service/CohortServiceTest.java b/src/test/java/nl/tudelft/submit/service/CohortServiceTest.java
index 48d20dd05d1585342be17069803e42d1be5e4d73..199ab8f368fe0722d59c838aef2760bf2a8ace4f 100644
--- a/src/test/java/nl/tudelft/submit/service/CohortServiceTest.java
+++ b/src/test/java/nl/tudelft/submit/service/CohortServiceTest.java
@@ -24,7 +24,6 @@ import java.util.List;
 
 import nl.tudelft.labracore.api.CohortControllerApi;
 import nl.tudelft.labracore.api.dto.*;
-import nl.tudelft.submit.TestSubmitApplication;
 
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -33,6 +32,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 
 import reactor.core.publisher.Flux;
+import application.test.TestSubmitApplication;
 
 @SpringBootTest(classes = TestSubmitApplication.class)
 public class CohortServiceTest {
diff --git a/src/test/java/nl/tudelft/submit/service/CourseServiceTest.java b/src/test/java/nl/tudelft/submit/service/CourseServiceTest.java
index c138028826aba589b186b35b78d3bdb4c689c375..47b2c845a02685bf9d02bbb798f5c2f4c206c49f 100644
--- a/src/test/java/nl/tudelft/submit/service/CourseServiceTest.java
+++ b/src/test/java/nl/tudelft/submit/service/CourseServiceTest.java
@@ -27,7 +27,6 @@ import java.util.List;
 import nl.tudelft.labracore.api.CourseControllerApi;
 import nl.tudelft.labracore.api.dto.CourseSummaryDTO;
 import nl.tudelft.labracore.api.dto.PersonIdDTO;
-import nl.tudelft.submit.TestSubmitApplication;
 
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -36,6 +35,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 
 import reactor.core.publisher.Flux;
+import application.test.TestSubmitApplication;
 
 @SpringBootTest(classes = TestSubmitApplication.class)
 public class CourseServiceTest {
diff --git a/src/test/java/nl/tudelft/submit/service/EditionServiceTest.java b/src/test/java/nl/tudelft/submit/service/EditionServiceTest.java
index c8648a67b9fd7bba2f9a30de220d0700f3e3da3e..100b95dcc9c26475fe487b3acdd9f5559b4dbcf5 100644
--- a/src/test/java/nl/tudelft/submit/service/EditionServiceTest.java
+++ b/src/test/java/nl/tudelft/submit/service/EditionServiceTest.java
@@ -34,7 +34,6 @@ import nl.tudelft.labracore.api.EditionControllerApi;
 import nl.tudelft.labracore.api.dto.*;
 import nl.tudelft.labracore.lib.api.GradeScheme;
 import nl.tudelft.labracore.lib.security.user.Person;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.cache.EditionCacheManager;
 import nl.tudelft.submit.cache.EditionRolesCacheManager;
 import nl.tudelft.submit.csv.CSVService;
@@ -61,6 +60,7 @@ import org.springframework.boot.test.mock.mockito.MockBean;
 
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
+import application.test.TestSubmitApplication;
 
 import com.opencsv.exceptions.CsvValidationException;
 
diff --git a/src/test/java/nl/tudelft/submit/service/EmailServiceTest.java b/src/test/java/nl/tudelft/submit/service/EmailServiceTest.java
index 51d4e89e92a5efbfbb40b21172b492fb3212a46b..3d88488594cd46bb198693e71729375c9216a7ba 100644
--- a/src/test/java/nl/tudelft/submit/service/EmailServiceTest.java
+++ b/src/test/java/nl/tudelft/submit/service/EmailServiceTest.java
@@ -22,7 +22,6 @@ import static org.mockito.Mockito.*;
 import java.util.List;
 
 import nl.tudelft.labracore.api.dto.PersonSummaryDTO;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.dto.create.MailMessageCreateDTO;
 
 import org.junit.jupiter.api.BeforeEach;
@@ -31,6 +30,8 @@ import org.mockito.MockitoAnnotations;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.boot.test.mock.mockito.MockBean;
 
+import application.test.TestSubmitApplication;
+
 @SpringBootTest(classes = TestSubmitApplication.class)
 public class EmailServiceTest {
 
diff --git a/src/test/java/nl/tudelft/submit/service/ExportServiceTest.java b/src/test/java/nl/tudelft/submit/service/ExportServiceTest.java
index 025dd2122c0615011cf631087f311225b21165ee..780d465c842f4bf6f67863918fbfb5ec4e4a2fb3 100644
--- a/src/test/java/nl/tudelft/submit/service/ExportServiceTest.java
+++ b/src/test/java/nl/tudelft/submit/service/ExportServiceTest.java
@@ -28,7 +28,6 @@ import javax.transaction.Transactional;
 
 import nl.tudelft.labracore.api.dto.*;
 import nl.tudelft.labracore.lib.api.GradeScheme;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.cache.EditionCacheManager;
 import nl.tudelft.submit.cache.ModuleCacheManager;
 import nl.tudelft.submit.csv.CSVService;
@@ -49,6 +48,8 @@ import org.springframework.beans.factory.annotation.Value;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.boot.test.mock.mockito.MockBean;
 
+import application.test.TestSubmitApplication;
+
 @Transactional
 @SpringBootTest(classes = TestSubmitApplication.class)
 class ExportServiceTest {
diff --git a/src/test/java/nl/tudelft/submit/service/FeedbackServiceMockRepoTest.java b/src/test/java/nl/tudelft/submit/service/FeedbackServiceMockRepoTest.java
index e2ea7a8a61969ec591b18e75cae7369cfb8152de..299e440701fae97383c943017db18f09f438c501 100644
--- a/src/test/java/nl/tudelft/submit/service/FeedbackServiceMockRepoTest.java
+++ b/src/test/java/nl/tudelft/submit/service/FeedbackServiceMockRepoTest.java
@@ -25,7 +25,6 @@ import java.time.LocalDateTime;
 import javax.transaction.Transactional;
 
 import nl.tudelft.librador.dto.view.View;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.dto.patch.FeedbackPatchDTO;
 import nl.tudelft.submit.dto.view.FeedbackViewDTO;
 import nl.tudelft.submit.model.Feedback;
@@ -38,11 +37,11 @@ import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.boot.test.mock.mockito.MockBean;
-import org.springframework.test.annotation.DirtiesContext;
+
+import application.test.TestSubmitApplication;
 
 @Transactional
 @SpringBootTest(classes = TestSubmitApplication.class)
-@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
 class FeedbackServiceMockRepoTest {
 
 	private final Long FEEDBACK_ID = 61L;
diff --git a/src/test/java/nl/tudelft/submit/service/FeedbackServiceSelfTest.java b/src/test/java/nl/tudelft/submit/service/FeedbackServiceSelfTest.java
index 363bc1bfe70d01d79e9c572881918773c715a75b..756094e816396324ae32a56b1390b7ac49566ded 100644
--- a/src/test/java/nl/tudelft/submit/service/FeedbackServiceSelfTest.java
+++ b/src/test/java/nl/tudelft/submit/service/FeedbackServiceSelfTest.java
@@ -26,13 +26,14 @@ import java.util.List;
 import java.util.Map;
 
 import nl.tudelft.labracore.api.dto.SubmissionSummaryDTO;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.dto.view.FeedbackViewDTO;
 
 import org.junit.jupiter.api.Test;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.boot.test.mock.mockito.MockBean;
 
+import application.test.TestSubmitApplication;
+
 @SpringBootTest(classes = TestSubmitApplication.class)
 class FeedbackServiceSelfTest {
 
diff --git a/src/test/java/nl/tudelft/submit/service/FeedbackServiceTest.java b/src/test/java/nl/tudelft/submit/service/FeedbackServiceTest.java
index a4708c12a366e1f979cce9ae98cebcb09298bead..aed0a7da91af8dc5c0fb88ef2637600bf33bdc1f 100644
--- a/src/test/java/nl/tudelft/submit/service/FeedbackServiceTest.java
+++ b/src/test/java/nl/tudelft/submit/service/FeedbackServiceTest.java
@@ -22,7 +22,6 @@ import static org.assertj.core.api.Assertions.assertThat;
 import java.time.LocalDateTime;
 
 import nl.tudelft.labracore.api.dto.*;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.dto.create.FeedbackCreateDTO;
 import nl.tudelft.submit.model.Feedback;
 import nl.tudelft.submit.repository.FeedbackRepository;
@@ -34,6 +33,8 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.boot.test.mock.mockito.MockBean;
 
+import application.test.TestSubmitApplication;
+
 import com.fasterxml.jackson.databind.ObjectMapper;
 
 @SpringBootTest(classes = TestSubmitApplication.class)
diff --git a/src/test/java/nl/tudelft/submit/service/FileServiceTest.java b/src/test/java/nl/tudelft/submit/service/FileServiceTest.java
index 85230470b3542bcc1592d4f5bd14b2827ad8776f..60783b254b7d2455ef4fe336f01ed64bed0c17ba 100644
--- a/src/test/java/nl/tudelft/submit/service/FileServiceTest.java
+++ b/src/test/java/nl/tudelft/submit/service/FileServiceTest.java
@@ -26,8 +26,6 @@ import java.nio.file.Paths;
 import java.util.HashMap;
 import java.util.Map;
 
-import nl.tudelft.submit.TestSubmitApplication;
-
 import org.assertj.core.util.Files;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -39,6 +37,8 @@ import org.springframework.mock.web.MockMultipartFile;
 import org.springframework.util.StringUtils;
 import org.springframework.web.multipart.MultipartFile;
 
+import application.test.TestSubmitApplication;
+
 @SpringBootTest(classes = TestSubmitApplication.class)
 class FileServiceTest {
 
diff --git a/src/test/java/nl/tudelft/submit/service/ModuleDivisionServiceSelfMockTest.java b/src/test/java/nl/tudelft/submit/service/ModuleDivisionServiceSelfMockTest.java
index 4ea2aa580da1f9f4c41e41fbd60748c442b5d0b3..4a7fd4ea617cd082e284a3e0c637dcc4ceb66e00 100644
--- a/src/test/java/nl/tudelft/submit/service/ModuleDivisionServiceSelfMockTest.java
+++ b/src/test/java/nl/tudelft/submit/service/ModuleDivisionServiceSelfMockTest.java
@@ -23,7 +23,6 @@ import java.util.List;
 
 import nl.tudelft.labracore.api.ModuleDivisionControllerApi;
 import nl.tudelft.labracore.api.dto.*;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.dto.create.DivisionsCreateTypeDTO;
 import nl.tudelft.submit.dto.create.ModuleDivisionImportDTO;
 import nl.tudelft.submit.enums.DivisionsCreateType;
@@ -37,6 +36,8 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.boot.test.mock.mockito.MockBean;
 
+import application.test.TestSubmitApplication;
+
 @SpringBootTest(classes = TestSubmitApplication.class)
 class ModuleDivisionServiceSelfMockTest {
 
diff --git a/src/test/java/nl/tudelft/submit/service/ModuleDivisionServiceTest.java b/src/test/java/nl/tudelft/submit/service/ModuleDivisionServiceTest.java
index 9fd7c97dc8438f1f1568f95f47ebe7b6c50b34cf..deaba78f41246d719ce32958dd96cd25b767550b 100644
--- a/src/test/java/nl/tudelft/submit/service/ModuleDivisionServiceTest.java
+++ b/src/test/java/nl/tudelft/submit/service/ModuleDivisionServiceTest.java
@@ -25,7 +25,6 @@ import java.util.List;
 
 import nl.tudelft.labracore.api.ModuleDivisionControllerApi;
 import nl.tudelft.labracore.api.dto.*;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.dto.create.ModuleDivisionImportDTO;
 import nl.tudelft.submit.enums.DivisionsGenerateOrder;
 import nl.tudelft.submit.enums.DivisionsGenerateType;
@@ -41,6 +40,7 @@ import org.springframework.boot.test.mock.mockito.MockBean;
 
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
+import application.test.TestSubmitApplication;
 
 @SpringBootTest(classes = TestSubmitApplication.class)
 class ModuleDivisionServiceTest {
diff --git a/src/test/java/nl/tudelft/submit/service/ModuleServiceTest.java b/src/test/java/nl/tudelft/submit/service/ModuleServiceTest.java
index f867243ca3777f7cbd3d08238199d31fdffdd502..19209a19de3e282ac1ca65beaeb4d07534aa4b3d 100644
--- a/src/test/java/nl/tudelft/submit/service/ModuleServiceTest.java
+++ b/src/test/java/nl/tudelft/submit/service/ModuleServiceTest.java
@@ -27,7 +27,6 @@ import javax.transaction.Transactional;
 import nl.tudelft.labracore.api.ModuleControllerApi;
 import nl.tudelft.labracore.api.dto.*;
 import nl.tudelft.labracore.lib.api.GradeScheme;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.cache.AssignmentCacheManager;
 import nl.tudelft.submit.cache.ModuleCacheManager;
 import nl.tudelft.submit.dto.create.DivisionsCreateTypeDTO;
@@ -46,6 +45,7 @@ import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.boot.test.mock.mockito.MockBean;
 
 import reactor.core.publisher.Mono;
+import application.test.TestSubmitApplication;
 
 @Transactional
 @SpringBootTest(classes = TestSubmitApplication.class)
diff --git a/src/test/java/nl/tudelft/submit/service/NoteServiceTest.java b/src/test/java/nl/tudelft/submit/service/NoteServiceTest.java
index db8cf34850d9098623c3b37e04cc27fb19ab3e88..990f84b141a9953767d929ac304ccd80e9bc9559 100644
--- a/src/test/java/nl/tudelft/submit/service/NoteServiceTest.java
+++ b/src/test/java/nl/tudelft/submit/service/NoteServiceTest.java
@@ -22,8 +22,6 @@ import static org.assertj.core.api.Assertions.assertThat;
 import javax.transaction.Transactional;
 
 import nl.tudelft.labracore.api.dto.*;
-import nl.tudelft.librador.SpringContext;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.dto.create.note.AssignmentNoteCreateDTO;
 import nl.tudelft.submit.dto.create.note.GroupNoteCreateDTO;
 import nl.tudelft.submit.dto.create.note.RoleNoteCreateDTO;
@@ -41,11 +39,12 @@ import nl.tudelft.submit.repository.note.GroupNoteRepository;
 import nl.tudelft.submit.repository.note.RoleNoteRepository;
 import nl.tudelft.submit.repository.note.SubmissionNoteRepository;
 
-import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 
+import application.test.TestSubmitApplication;
+
 @Transactional
 @SpringBootTest(classes = TestSubmitApplication.class)
 class NoteServiceTest {
@@ -53,19 +52,15 @@ class NoteServiceTest {
 	@Autowired
 	private NoteService service;
 
+	@Autowired
 	private RoleNoteRepository rnr;
+	@Autowired
 	private GroupNoteRepository gnr;
+	@Autowired
 	private SubmissionNoteRepository snr;
+	@Autowired
 	private AssignmentNoteRepository anr;
 
-	@BeforeEach
-	void setUp() {
-		rnr = SpringContext.getBean(RoleNoteRepository.class);
-		gnr = SpringContext.getBean(GroupNoteRepository.class);
-		snr = SpringContext.getBean(SubmissionNoteRepository.class);
-		anr = SpringContext.getBean(AssignmentNoteRepository.class);
-	}
-
 	@Test
 	void addOrUpdateRoleNote() {
 		RoleId roleId = new RoleId(new Id().editionId(6854L).personId(56984L));
diff --git a/src/test/java/nl/tudelft/submit/service/NotificationServiceTest.java b/src/test/java/nl/tudelft/submit/service/NotificationServiceTest.java
index 270a6574a69f78d48636ba99a58c7c76ba064c3e..a0a79839266d5ec8b07cfe5145969f60b1c9c2a1 100644
--- a/src/test/java/nl/tudelft/submit/service/NotificationServiceTest.java
+++ b/src/test/java/nl/tudelft/submit/service/NotificationServiceTest.java
@@ -24,7 +24,6 @@ import static org.mockito.Mockito.when;
 
 import java.util.List;
 
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.dto.create.NotificationCreateDTO;
 import nl.tudelft.submit.dto.view.NotificationViewDTO;
 import nl.tudelft.submit.model.Notification;
@@ -37,6 +36,8 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.boot.test.mock.mockito.MockBean;
 
+import application.test.TestSubmitApplication;
+
 @SpringBootTest(classes = TestSubmitApplication.class)
 public class NotificationServiceTest {
 
diff --git a/src/test/java/nl/tudelft/submit/service/PersonServiceTest.java b/src/test/java/nl/tudelft/submit/service/PersonServiceTest.java
index 5f6dd8bd9f61be17fd0f8c5b0b8d6ced15c97dd9..a3c2ee6e53f3c1b716ae7e9caff6dd53f7cf2ae9 100644
--- a/src/test/java/nl/tudelft/submit/service/PersonServiceTest.java
+++ b/src/test/java/nl/tudelft/submit/service/PersonServiceTest.java
@@ -26,7 +26,6 @@ import java.util.List;
 
 import nl.tudelft.labracore.api.PersonControllerApi;
 import nl.tudelft.labracore.api.dto.*;
-import nl.tudelft.submit.TestSubmitApplication;
 
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -36,6 +35,7 @@ import org.springframework.boot.test.mock.mockito.MockBean;
 
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
+import application.test.TestSubmitApplication;
 
 @SpringBootTest(classes = TestSubmitApplication.class)
 class PersonServiceTest {
diff --git a/src/test/java/nl/tudelft/submit/service/RoleServiceTest.java b/src/test/java/nl/tudelft/submit/service/RoleServiceTest.java
index d9cfae70c41d033cdfc165534a4bb2f092d450e8..a1ac2a8c070c0d57392753379b33c7cf954274fc 100644
--- a/src/test/java/nl/tudelft/submit/service/RoleServiceTest.java
+++ b/src/test/java/nl/tudelft/submit/service/RoleServiceTest.java
@@ -32,7 +32,6 @@ import nl.tudelft.labracore.api.EditionControllerApi;
 import nl.tudelft.labracore.api.RoleControllerApi;
 import nl.tudelft.labracore.api.dto.*;
 import nl.tudelft.librador.dto.view.View;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.cache.RoleCacheManager;
 import nl.tudelft.submit.dto.id.RoleId;
 import nl.tudelft.submit.dto.view.labracore.MemberViewDTO;
@@ -40,7 +39,6 @@ import nl.tudelft.submit.model.Signature;
 import nl.tudelft.submit.model.note.RoleNote;
 import nl.tudelft.submit.repository.note.RoleNoteRepository;
 
-import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
@@ -48,6 +46,7 @@ import org.springframework.boot.test.mock.mockito.MockBean;
 
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
+import application.test.TestSubmitApplication;
 
 @Transactional
 @SpringBootTest(classes = TestSubmitApplication.class)
@@ -75,11 +74,6 @@ class RoleServiceTest {
 	@MockBean
 	private RoleCacheManager roleCache;
 
-	@BeforeEach
-	public void clearDB() {
-		roleNoteRepository.deleteAll();
-	}
-
 	@Test
 	void getRolesOfEdition() {
 		RolePersonDetailsDTO p1 = new RolePersonDetailsDTO();
diff --git a/src/test/java/nl/tudelft/submit/service/ScriptServiceTest.java b/src/test/java/nl/tudelft/submit/service/ScriptServiceTest.java
index 6e314a8d69af9df4af7bf5fa0b0b9418d9f03348..31d12b1d0143339d175ba0e52b0b8087a2a9a0fd 100644
--- a/src/test/java/nl/tudelft/submit/service/ScriptServiceTest.java
+++ b/src/test/java/nl/tudelft/submit/service/ScriptServiceTest.java
@@ -27,7 +27,6 @@ import java.util.Optional;
 import javax.transaction.Transactional;
 
 import nl.tudelft.labracore.lib.api.GradeScheme;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.dto.create.script.ScriptCreateDTO;
 import nl.tudelft.submit.dto.create.script.ScriptTrainCreateDTO;
 import nl.tudelft.submit.dto.create.script.ScriptWagonCreateDTO;
@@ -58,6 +57,8 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.boot.test.mock.mockito.MockBean;
 
+import application.test.TestSubmitApplication;
+
 @Transactional
 @SpringBootTest(classes = TestSubmitApplication.class)
 class ScriptServiceTest {
diff --git a/src/test/java/nl/tudelft/submit/service/StatisticsServiceTest.java b/src/test/java/nl/tudelft/submit/service/StatisticsServiceTest.java
index 4cdd6f724e2eaa4b2905d1f53a1a4ed88d97e492..9d8662bff8582601d43b1b74395883f00dfa2134 100644
--- a/src/test/java/nl/tudelft/submit/service/StatisticsServiceTest.java
+++ b/src/test/java/nl/tudelft/submit/service/StatisticsServiceTest.java
@@ -30,7 +30,6 @@ import javax.transaction.Transactional;
 
 import nl.tudelft.labracore.api.dto.*;
 import nl.tudelft.labracore.lib.api.GradeScheme;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.dto.view.labracore.SubmitSubmissionViewDTO;
 import nl.tudelft.submit.dto.view.statistics.*;
 import nl.tudelft.submit.model.SubmitEdition;
@@ -43,6 +42,8 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.boot.test.mock.mockito.MockBean;
 
+import application.test.TestSubmitApplication;
+
 @Transactional
 @SpringBootTest(classes = TestSubmitApplication.class)
 class StatisticsServiceTest {
diff --git a/src/test/java/nl/tudelft/submit/service/StudentGroupServiceTest.java b/src/test/java/nl/tudelft/submit/service/StudentGroupServiceTest.java
index 25d73b5f1907f6c2127f3625d45a2b76e55f2b6b..32786a31c061f9bb153d27c6a8ad32ebb7f38aa1 100644
--- a/src/test/java/nl/tudelft/submit/service/StudentGroupServiceTest.java
+++ b/src/test/java/nl/tudelft/submit/service/StudentGroupServiceTest.java
@@ -31,7 +31,6 @@ import javax.transaction.Transactional;
 import nl.tudelft.labracore.api.StudentGroupControllerApi;
 import nl.tudelft.labracore.api.dto.*;
 import nl.tudelft.librador.dto.view.View;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.cache.StudentGroupCacheManager;
 import nl.tudelft.submit.dto.id.GroupId;
 import nl.tudelft.submit.dto.view.labracore.SubmitGroupDetailsDTO;
@@ -43,16 +42,15 @@ import nl.tudelft.submit.model.note.GroupNote;
 import nl.tudelft.submit.repository.SwitchEventRepository;
 import nl.tudelft.submit.repository.note.GroupNoteRepository;
 
-import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.boot.test.mock.mockito.MockBean;
 
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
+import application.test.TestSubmitApplication;
 
 @Transactional
 @SpringBootTest(classes = TestSubmitApplication.class)
@@ -93,12 +91,6 @@ class StudentGroupServiceTest {
 	@MockBean
 	private SubmissionService submissionService;
 
-	@BeforeEach
-	void setUp() {
-		MockitoAnnotations.initMocks(this);
-		groupNoteRepository.deleteAll();
-	}
-
 	@Test
 	void getGroupDetails() {
 		StudentGroupDetailsDTO dto = new StudentGroupDetailsDTO().id(GROUP_ID).name("Test group")
diff --git a/src/test/java/nl/tudelft/submit/service/SubmissionServiceTest.java b/src/test/java/nl/tudelft/submit/service/SubmissionServiceTest.java
index c59ecba11fe515736426c088f12639995a7e4463..0fd529bd968245f430e3b13948f7f4c962a06efe 100644
--- a/src/test/java/nl/tudelft/submit/service/SubmissionServiceTest.java
+++ b/src/test/java/nl/tudelft/submit/service/SubmissionServiceTest.java
@@ -32,10 +32,10 @@ import javax.transaction.Transactional;
 
 import nl.tudelft.labracore.api.SubmissionControllerApi;
 import nl.tudelft.labracore.api.dto.*;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.cache.SubmissionCacheManager;
 import nl.tudelft.submit.dto.create.SubmissionDownloadConfigCreateDTO;
 import nl.tudelft.submit.enums.SubmissionDownloadPreference;
+import nl.tudelft.submit.repository.SubmitSubmissionRepository;
 
 import org.assertj.core.api.Assertions;
 import org.junit.jupiter.api.Test;
@@ -46,6 +46,7 @@ import org.springframework.boot.test.mock.mockito.MockBean;
 
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
+import application.test.TestSubmitApplication;
 
 @Transactional
 @SpringBootTest(classes = TestSubmitApplication.class)
@@ -79,6 +80,9 @@ class SubmissionServiceTest {
 	@Autowired
 	private SubmissionService submissionService;
 
+	@Autowired
+	private SubmitSubmissionRepository submissionRepository;
+
 	@MockBean
 	private AssignmentService assignmentService;
 
diff --git a/src/test/java/nl/tudelft/submit/service/SubmitGroupServiceTest.java b/src/test/java/nl/tudelft/submit/service/SubmitGroupServiceTest.java
index a31eec2f6f83193b56c068fb1d2ad1ccfd394f5d..849976244a13b256d57e3213c30638a5f56241f7 100644
--- a/src/test/java/nl/tudelft/submit/service/SubmitGroupServiceTest.java
+++ b/src/test/java/nl/tudelft/submit/service/SubmitGroupServiceTest.java
@@ -24,7 +24,6 @@ import java.util.Optional;
 
 import javax.transaction.Transactional;
 
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.model.SubmitGroup;
 import nl.tudelft.submit.repository.SubmitGroupRepository;
 
@@ -33,6 +32,8 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.boot.test.mock.mockito.MockBean;
 
+import application.test.TestSubmitApplication;
+
 @Transactional
 @SpringBootTest(classes = TestSubmitApplication.class)
 class SubmitGroupServiceTest {
diff --git a/src/test/java/nl/tudelft/submit/service/TemplateServiceTest.java b/src/test/java/nl/tudelft/submit/service/TemplateServiceTest.java
index b9cd5e2b593f1356089c904ae16a071c1e688521..a9aa570f55e33350ff657b89a234f23285309dbc 100644
--- a/src/test/java/nl/tudelft/submit/service/TemplateServiceTest.java
+++ b/src/test/java/nl/tudelft/submit/service/TemplateServiceTest.java
@@ -24,12 +24,13 @@ import java.util.List;
 import java.util.stream.Collectors;
 
 import lombok.Data;
-import nl.tudelft.submit.TestSubmitApplication;
 
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 
+import application.test.TestSubmitApplication;
+
 @SpringBootTest(classes = TestSubmitApplication.class)
 public class TemplateServiceTest {
 
diff --git a/src/test/java/nl/tudelft/submit/service/UserSettingsServiceTest.java b/src/test/java/nl/tudelft/submit/service/UserSettingsServiceTest.java
index 2a1fb126591fb6b70954bbe47cef7f1d0f37b3fb..106d4bdbc4ca2ac8fc220bbbf82ca2f8f71f1ea3 100644
--- a/src/test/java/nl/tudelft/submit/service/UserSettingsServiceTest.java
+++ b/src/test/java/nl/tudelft/submit/service/UserSettingsServiceTest.java
@@ -21,20 +21,21 @@ import static org.assertj.core.api.Assertions.assertThat;
 
 import java.util.List;
 
+import javax.transaction.Transactional;
+
 import nl.tudelft.labracore.api.dto.*;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.dto.patch.UserSettingsPatchDTO;
 import nl.tudelft.submit.enums.NotificationPreference;
 import nl.tudelft.submit.model.UserSettings;
 import nl.tudelft.submit.repository.UserSettingsRepository;
 
-import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.test.annotation.DirtiesContext;
 
-@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
+import application.test.TestSubmitApplication;
+
+@Transactional
 @SpringBootTest(classes = TestSubmitApplication.class)
 class UserSettingsServiceTest {
 
@@ -47,11 +48,6 @@ class UserSettingsServiceTest {
 	@Autowired
 	private UserSettingsService service;
 
-	@BeforeEach
-	public void clearDB() {
-		repository.deleteAll();
-	}
-
 	@Test
 	void getOrCreateUserSettingsCreatesWhenNoneExists() {
 		assertThat(repository.findByPersonId(PERSON_ID)).isNotPresent();
diff --git a/src/test/java/nl/tudelft/submit/service/VersionServiceTest.java b/src/test/java/nl/tudelft/submit/service/VersionServiceTest.java
index b7ae57168d9274079aa6ecafa9bc42032be16b1a..46c7887571e87805d5868e59af77ff4262ecb568 100644
--- a/src/test/java/nl/tudelft/submit/service/VersionServiceTest.java
+++ b/src/test/java/nl/tudelft/submit/service/VersionServiceTest.java
@@ -29,7 +29,6 @@ import java.util.List;
 import java.util.Optional;
 
 import nl.tudelft.labracore.api.dto.*;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.dto.create.VersionCreateDTO;
 import nl.tudelft.submit.dto.id.SubmitGroupIdDTO;
 import nl.tudelft.submit.dto.patch.SubmitAssignmentPatchDTO;
@@ -47,6 +46,8 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.boot.test.mock.mockito.MockBean;
 
+import application.test.TestSubmitApplication;
+
 @SpringBootTest(classes = TestSubmitApplication.class)
 class VersionServiceTest {
 
diff --git a/src/test/java/nl/tudelft/submit/service/grading/EditionFormulaServiceTest.java b/src/test/java/nl/tudelft/submit/service/grading/EditionFormulaServiceTest.java
index ae71f0b00ba3ea089f858eca4bf7c6f3a4df5218..a638ab0690d2ff554b1323bd751da08f1db54e18 100644
--- a/src/test/java/nl/tudelft/submit/service/grading/EditionFormulaServiceTest.java
+++ b/src/test/java/nl/tudelft/submit/service/grading/EditionFormulaServiceTest.java
@@ -25,7 +25,6 @@ import javax.transaction.Transactional;
 
 import nl.tudelft.labracore.api.dto.EditionIdDTO;
 import nl.tudelft.labracore.lib.api.GradeScheme;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.dto.create.grading.GradingFormulaCreateDTO;
 import nl.tudelft.submit.enums.GradableType;
 import nl.tudelft.submit.model.grading.EditionGradingFormula;
@@ -38,9 +37,9 @@ import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.boot.test.mock.mockito.MockBean;
-import org.springframework.test.annotation.DirtiesContext;
 
-@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
+import application.test.TestSubmitApplication;
+
 @Transactional
 @SpringBootTest(classes = TestSubmitApplication.class)
 public class EditionFormulaServiceTest {
@@ -62,7 +61,6 @@ public class EditionFormulaServiceTest {
 	public void init() {
 		create = new GradingFormulaCreateDTO("test formula", GradableType.EDITION, ENTITY_ID,
 				GradeScheme.DUTCH_GRADE);
-		repository.deleteAll();
 	}
 
 	@Test
diff --git a/src/test/java/nl/tudelft/submit/service/grading/FormulaServiceTest.java b/src/test/java/nl/tudelft/submit/service/grading/FormulaServiceTest.java
index 44be4706e826de507f2d4f327d109f409b01bd51..515b6719ed94ad93202afca2e54ec45928a0b42d 100644
--- a/src/test/java/nl/tudelft/submit/service/grading/FormulaServiceTest.java
+++ b/src/test/java/nl/tudelft/submit/service/grading/FormulaServiceTest.java
@@ -25,7 +25,6 @@ import java.util.List;
 import javax.transaction.Transactional;
 
 import nl.tudelft.labracore.lib.api.GradeScheme;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.dto.helper.Expression;
 import nl.tudelft.submit.dto.patch.grading.GradingFormulaPatchDTO;
 import nl.tudelft.submit.exception.InvalidFormulaException;
@@ -39,6 +38,8 @@ import org.junit.jupiter.params.provider.MethodSource;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 
+import application.test.TestSubmitApplication;
+
 @Transactional
 @SpringBootTest(classes = TestSubmitApplication.class)
 public class FormulaServiceTest {
diff --git a/src/test/java/nl/tudelft/submit/service/grading/GradeServiceTest.java b/src/test/java/nl/tudelft/submit/service/grading/GradeServiceTest.java
index 6b3dad3ea1fab24832133ff7c44805b57e922dbc..ff9e7033c801bd130fbd101dfeba5fcb64b8f26d 100644
--- a/src/test/java/nl/tudelft/submit/service/grading/GradeServiceTest.java
+++ b/src/test/java/nl/tudelft/submit/service/grading/GradeServiceTest.java
@@ -39,7 +39,6 @@ import nl.tudelft.labracore.api.GradeControllerApi;
 import nl.tudelft.labracore.api.PersonControllerApi;
 import nl.tudelft.labracore.api.dto.*;
 import nl.tudelft.labracore.lib.api.GradeScheme;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.cache.EditionCacheManager;
 import nl.tudelft.submit.cache.SubmissionCacheManager;
 import nl.tudelft.submit.dto.create.grading.GradingFormulaCreateDTO;
@@ -54,11 +53,9 @@ import nl.tudelft.submit.repository.grading.ModuleGradingFormulaRepository;
 import nl.tudelft.submit.service.FormulaService;
 import nl.tudelft.submit.service.GradeService;
 
-import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.CsvSource;
-import org.mockito.MockitoAnnotations;
 import org.modelmapper.ModelMapper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
@@ -66,6 +63,7 @@ import org.springframework.boot.test.mock.mockito.MockBean;
 
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
+import application.test.TestSubmitApplication;
 
 @Transactional
 @SpringBootTest(classes = TestSubmitApplication.class)
@@ -116,11 +114,6 @@ public class GradeServiceTest {
 	@Autowired
 	private PersonControllerApi personApi;
 
-	@BeforeEach
-	void setUp() {
-		MockitoAnnotations.initMocks(this);
-	}
-
 	@Test
 	public void executeSimpleFormula() {
 		ModuleCalculatedScore expected = ModuleCalculatedScore.builder()
diff --git a/src/test/java/nl/tudelft/submit/service/grading/ModuleFormulaServiceTest.java b/src/test/java/nl/tudelft/submit/service/grading/ModuleFormulaServiceTest.java
index 1e89357464403851c5771595c83211cb42e9d782..5c58ef2b395aabd6a6e68e6cdb5adf5368119962 100644
--- a/src/test/java/nl/tudelft/submit/service/grading/ModuleFormulaServiceTest.java
+++ b/src/test/java/nl/tudelft/submit/service/grading/ModuleFormulaServiceTest.java
@@ -25,7 +25,6 @@ import javax.transaction.Transactional;
 
 import nl.tudelft.labracore.api.dto.ModuleIdDTO;
 import nl.tudelft.labracore.lib.api.GradeScheme;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.dto.create.grading.GradingFormulaCreateDTO;
 import nl.tudelft.submit.enums.GradableType;
 import nl.tudelft.submit.model.grading.ModuleGradingFormula;
@@ -38,9 +37,9 @@ import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.boot.test.mock.mockito.MockBean;
-import org.springframework.test.annotation.DirtiesContext;
 
-@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
+import application.test.TestSubmitApplication;
+
 @Transactional
 @SpringBootTest(classes = TestSubmitApplication.class)
 public class ModuleFormulaServiceTest {
@@ -62,8 +61,6 @@ public class ModuleFormulaServiceTest {
 	public void init() {
 		create = new GradingFormulaCreateDTO("test formula", GradableType.MODULE, ENTITY_ID,
 				GradeScheme.DUTCH_GRADE);
-
-		repository.deleteAll();
 	}
 
 	@Test
diff --git a/src/test/java/nl/tudelft/submit/service/grading/ModuleGradingTest.java b/src/test/java/nl/tudelft/submit/service/grading/ModuleGradingTest.java
index 9871ee725ffcbb4d1857e577429db861a3b1c282..b06207bf299cd2ed8372deee1c5a6bdc147c3c49 100644
--- a/src/test/java/nl/tudelft/submit/service/grading/ModuleGradingTest.java
+++ b/src/test/java/nl/tudelft/submit/service/grading/ModuleGradingTest.java
@@ -31,7 +31,6 @@ import javax.transaction.Transactional;
 import nl.tudelft.labracore.api.*;
 import nl.tudelft.labracore.api.dto.*;
 import nl.tudelft.labracore.lib.api.GradeScheme;
-import nl.tudelft.submit.TestSubmitApplication;
 import nl.tudelft.submit.cache.ModuleCacheManager;
 import nl.tudelft.submit.dto.create.NotificationCreateDTO;
 import nl.tudelft.submit.enums.GradableType;
@@ -57,6 +56,7 @@ import org.springframework.boot.test.mock.mockito.MockBean;
 
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
+import application.test.TestSubmitApplication;
 
 @Transactional
 @SpringBootTest(classes = TestSubmitApplication.class)
@@ -183,9 +183,6 @@ public class ModuleGradingTest {
 		when(moduleCache.getOrThrow(anyLong())).thenReturn(MODULE_DETAILS);
 		when(moduleApi.getModuleById(anyLong())).thenReturn(Mono.just(MODULE_DETAILS));
 		when(personApi.getPeopleByEditionAndRoleType(anyLong(), anyString())).thenReturn(Flux.just(PERSON));
-
-		moduleScoreRepository.deleteAll();
-		switchEventRepository.deleteAll();
 	}
 
 	@Test
diff --git a/src/test/java/nl/tudelft/submit/smoke/DevSubmitApplicationTest.java b/src/test/java/nl/tudelft/submit/smoke/DevSubmitApplicationTest.java
index f59f974378a587f5de06d3d3e644cf957a0a7b57..ab9c669b5ec6a49f8bbc36b21017460fdb7e76d7 100644
--- a/src/test/java/nl/tudelft/submit/smoke/DevSubmitApplicationTest.java
+++ b/src/test/java/nl/tudelft/submit/smoke/DevSubmitApplicationTest.java
@@ -19,17 +19,19 @@ package nl.tudelft.submit.smoke;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
-import nl.tudelft.submit.DevSubmitApplication;
-
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.annotation.DirtiesContext;
 import org.springframework.web.servlet.LocaleResolver;
 import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
 
+import application.dev.DevSubmitApplication;
+
 @Nested
 @SpringBootTest(classes = DevSubmitApplication.class)
+@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
 public class DevSubmitApplicationTest extends ApplicationTestClass {
 
 	@Autowired
diff --git a/src/test/java/nl/tudelft/submit/TestSubmitApplication.java b/src/test/java/nl/tudelft/submit/smoke/MigrationApplicationTest.java
similarity index 60%
rename from src/test/java/nl/tudelft/submit/TestSubmitApplication.java
rename to src/test/java/nl/tudelft/submit/smoke/MigrationApplicationTest.java
index 55c8441efaaf8a73b9b93b07892926832bb81248..542376e9fd506ebca8bcd1271ddf04d79255c72a 100644
--- a/src/test/java/nl/tudelft/submit/TestSubmitApplication.java
+++ b/src/test/java/nl/tudelft/submit/smoke/MigrationApplicationTest.java
@@ -15,18 +15,18 @@
  * You should have received a copy of the GNU Affero General Public License
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
-package nl.tudelft.submit;
+package nl.tudelft.submit.smoke;
 
-import org.springframework.boot.SpringApplication;
-import org.springframework.context.annotation.PropertySource;
-import org.springframework.context.annotation.PropertySources;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.annotation.DirtiesContext;
 
-@PropertySources({
-		@PropertySource("classpath:application.yaml"),
-		@PropertySource("classpath:application-test.properties")
-})
-public class TestSubmitApplication extends SubmitApplication {
-	public static void main(String[] args) {
-		SpringApplication.run(TestSubmitApplication.class, args);
+import application.migration.db.MigrationSubmitApplication;
+
+@SpringBootTest(classes = MigrationSubmitApplication.class)
+@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
+public class MigrationApplicationTest extends SmokeTest.TestClass {
+	@Override
+	protected String getExpectedDdl() {
+		return "validate";
 	}
 }
diff --git a/src/main/resources/static/js/main.js b/src/test/java/nl/tudelft/submit/smoke/NoDbMigrationApplicationTest.java
similarity index 52%
rename from src/main/resources/static/js/main.js
rename to src/test/java/nl/tudelft/submit/smoke/NoDbMigrationApplicationTest.java
index cd35221cc5eb2eee9e314892349045b7d60f516e..e15e6bcc11f913ed2b2c7adf65ef4d01c511e4c9 100644
--- a/src/main/resources/static/js/main.js
+++ b/src/test/java/nl/tudelft/submit/smoke/NoDbMigrationApplicationTest.java
@@ -15,25 +15,18 @@
  * You should have received a copy of the GNU Affero General Public License
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
+package nl.tudelft.submit.smoke;
 
-let overlayStack = [];
-function toggleOverlay(id) {
-    if (overlayStack.length > 0 && overlayStack[overlayStack.length-1] !== id) {
-        document.getElementById(overlayStack[overlayStack.length-1]).classList.add("hidden");
-    }
-    let ovl = document.getElementById(id);
-    ovl.classList.toggle("hidden");
-    if (ovl.classList.contains("hidden")) {
-        overlayStack.pop();
-        if (overlayStack.length > 0) {
-            document.getElementById(overlayStack[overlayStack.length-1]).classList.remove("hidden");
-        }
-    } else {
-        overlayStack.push(id)
-    }
-}
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.annotation.DirtiesContext;
+
+import application.migration.nodb.NoDbMigrationSubmitApplication;
 
-function submitForm(id) {
-    let form = document.getElementById(id);
-    form.submit();
-}
\ No newline at end of file
+@SpringBootTest(classes = NoDbMigrationSubmitApplication.class)
+@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
+public class NoDbMigrationApplicationTest extends SmokeTest.TestClass {
+	@Override
+	protected String getExpectedDdl() {
+		return "validate";
+	}
+}
diff --git a/src/test/java/nl/tudelft/submit/smoke/SmokeTest.java b/src/test/java/nl/tudelft/submit/smoke/SmokeTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..842f59a51cb1c2c3b44a0f1c13d04046c7dc1971
--- /dev/null
+++ b/src/test/java/nl/tudelft/submit/smoke/SmokeTest.java
@@ -0,0 +1,79 @@
+/*
+ * Submit
+ * Copyright (C) 2020 - Delft University of Technology
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+package nl.tudelft.submit.smoke;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.orm.jpa.HibernateProperties;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.annotation.DirtiesContext;
+
+import application.dev.DevSubmitApplication;
+import application.nodb.NoDbTestSubmitApplication;
+import application.test.TestSubmitApplication;
+
+public class SmokeTest {
+	static abstract class TestClass {
+		@Autowired
+		private HibernateProperties hp;
+
+		@Test
+		public void smoke() {
+		}
+
+		@Test
+		void hibernateDdlIsSetCorrectly() {
+			assertThat(hp.getDdlAuto()).isEqualTo(getExpectedDdl());
+		}
+
+		protected abstract String getExpectedDdl();
+	}
+
+	@Nested
+	@SpringBootTest(classes = DevSubmitApplication.class)
+	@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
+	public class DevelopmentApplicationWithValidateTest extends TestClass {
+		@Override
+		protected String getExpectedDdl() {
+			return "validate";
+		}
+	}
+
+	@Nested
+	@SpringBootTest(classes = TestSubmitApplication.class)
+	@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
+	public class TestApplicationTest extends TestClass {
+		@Override
+		protected String getExpectedDdl() {
+			return "validate";
+		}
+	}
+
+	@Nested
+	@SpringBootTest(classes = NoDbTestSubmitApplication.class)
+	@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
+	public class NoDbTestApplicationTest extends TestClass {
+		@Override
+		protected String getExpectedDdl() {
+			return "validate";
+		}
+	}
+}
diff --git a/src/test/java/nl/tudelft/submit/smoke/TestSubmitApplicationTest.java b/src/test/java/nl/tudelft/submit/smoke/TestSubmitApplicationTest.java
index 73ca7dabd45a2985c48ea8447c5c846281a6776e..535221d525d27dc684d212ac076b6ca7ee44d464 100644
--- a/src/test/java/nl/tudelft/submit/smoke/TestSubmitApplicationTest.java
+++ b/src/test/java/nl/tudelft/submit/smoke/TestSubmitApplicationTest.java
@@ -17,12 +17,14 @@
  */
 package nl.tudelft.submit.smoke;
 
-import nl.tudelft.submit.TestSubmitApplication;
-
 import org.junit.jupiter.api.Nested;
 import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.annotation.DirtiesContext;
+
+import application.test.TestSubmitApplication;
 
 @Nested
 @SpringBootTest(classes = TestSubmitApplication.class)
+@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
 public class TestSubmitApplicationTest extends ApplicationTestClass {
 }
diff --git a/src/test/resources/application-dev.properties b/src/test/resources/application-dev.properties
index 181440cac67eb783f7ce2b84aaec4d4591af9f02..02c33af071e077bdc930c8fa5e0167ce433562bf 100644
--- a/src/test/resources/application-dev.properties
+++ b/src/test/resources/application-dev.properties
@@ -18,7 +18,8 @@
 
 spring.profiles.active=test-dev
 
-spring.jpa.hibernate.ddl-auto=create
+spring.jpa.hibernate.ddl-auto=validate
 spring.liquibase.enabled=true
 
-spring.datasource.url=jdbc:h2:mem:devdb
\ No newline at end of file
+spring.datasource.url=jdbc:h2:mem:devdb
+spring.liquibase.change-log=classpath:/changelog-test.yaml
\ No newline at end of file
diff --git a/src/test/resources/application-h2-file.properties b/src/test/resources/application-h2-file.properties
new file mode 100644
index 0000000000000000000000000000000000000000..f0425afb74fd37e6524cd1c472ecfe744cac4371
--- /dev/null
+++ b/src/test/resources/application-h2-file.properties
@@ -0,0 +1,29 @@
+#
+# Submit
+# Copyright (C) 2020 - Delft University of Technology
+#
+# 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 <https://www.gnu.org/licenses/>.
+#
+
+# Default application properties. These can be overridden at run time from an
+# application.properties file in the same directory as the executable jar.
+
+spring.profiles.active=testing
+
+spring.jpa.hibernate.ddl-auto=create-drop
+
+spring.liquibase.change-log=classpath:/changelog-test.yaml
+spring.liquibase.enabled=true
+
+spring.datasource.url=jdbc:h2:file:./test-data
diff --git a/src/test/resources/application-h2.properties b/src/test/resources/application-h2.properties
new file mode 100644
index 0000000000000000000000000000000000000000..dcf99ccdad2ebbe20e8f908c27bb8ab5af29190c
--- /dev/null
+++ b/src/test/resources/application-h2.properties
@@ -0,0 +1,29 @@
+#
+# Submit
+# Copyright (C) 2020 - Delft University of Technology
+#
+# 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 <https://www.gnu.org/licenses/>.
+#
+
+# Default application properties. These can be overridden at run time from an
+# application.properties file in the same directory as the executable jar.
+
+spring.profiles.active=testing
+
+spring.jpa.hibernate.ddl-auto=validate
+
+spring.liquibase.change-log=classpath:/changelog-test.yaml
+spring.liquibase.enabled=true
+
+spring.datasource.url=jdbc:h2:mem:testdb
diff --git a/src/test/resources/application-migration.properties b/src/test/resources/application-migration.properties
new file mode 100644
index 0000000000000000000000000000000000000000..dba5b08c0ef318d59bed71365fba5dadc3339d19
--- /dev/null
+++ b/src/test/resources/application-migration.properties
@@ -0,0 +1,27 @@
+#
+# Submit
+# Copyright (C) 2020 - Delft University of Technology
+#
+# 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 <https://www.gnu.org/licenses/>.
+#
+
+# Default application properties. These can be overridden at run time from an
+# application.properties file in the same directory as the executable jar.
+
+spring.profiles.active=migration
+
+spring.jpa.hibernate.ddl-auto=validate
+
+spring.liquibase.change-log=classpath:/changelog-test.yaml
+spring.liquibase.enabled=true
diff --git a/src/test/resources/application-mysql.properties b/src/test/resources/application-mysql.properties
new file mode 100644
index 0000000000000000000000000000000000000000..d32abafc5731e50cd3eb7062cbe1b142251f9c3c
--- /dev/null
+++ b/src/test/resources/application-mysql.properties
@@ -0,0 +1,36 @@
+#
+# Submit
+# Copyright (C) 2020 - Delft University of Technology
+#
+# 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 <https://www.gnu.org/licenses/>.
+#
+
+spring.profiles.active=test
+
+spring.jpa.hibernate.ddl-auto=validate
+spring.jpa.database-platform=org.hibernate.dialect.MySQL57InnoDBDialect
+spring.jpa.properties.hibernate.globally_quoted_identifiers=true
+spring.jpa.properties.hibernate.keyword_auto_quoting_enabled=false
+spring.jpa.properties.hibernate.globally_quoted_identifiers_skip_column_definitions=true
+
+spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
+spring.datasource.url=jdbc:mysql://mysql/__db_name__?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8
+spring.datasource.username=__username__
+spring.datasource.password=__password__
+
+spring.datasource.hikari.minimum-idle=1
+spring.datasource.hikari.maximum-pool-size=4
+
+spring.liquibase.enabled=true
+spring.liquibase.change-log=classpath:/changelog-test-db.yaml
diff --git a/src/test/resources/application-postgresql.properties b/src/test/resources/application-postgresql.properties
new file mode 100644
index 0000000000000000000000000000000000000000..2feaaec4a3b8adc05243e19d74db79f95b7ee211
--- /dev/null
+++ b/src/test/resources/application-postgresql.properties
@@ -0,0 +1,36 @@
+#
+# Submit
+# Copyright (C) 2020 - Delft University of Technology
+#
+# 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 <https://www.gnu.org/licenses/>.
+#
+
+spring.profiles.active=test
+
+spring.jpa.database-platform=org.hibernate.dialect.PostgreSQL10Dialect
+spring.jpa.hibernate.ddl-auto=validate
+spring.jpa.properties.hibernate.globally_quoted_identifiers=true
+spring.jpa.properties.hibernate.keyword_auto_quoting_enabled=false
+spring.jpa.properties.hibernate.globally_quoted_identifiers_skip_column_definitions=true
+
+spring.datasource.driver-class-name=org.postgresql.Driver
+spring.datasource.url=jdbc:postgresql://postgres/__db_name__
+spring.datasource.username=__username__
+spring.datasource.password=__password__
+
+spring.datasource.hikari.minimum-idle=1
+spring.datasource.hikari.maximum-pool-size=4
+
+spring.liquibase.enabled=true
+spring.liquibase.change-log=classpath:/changelog-test-db.yaml
diff --git a/src/test/resources/application-test.properties b/src/test/resources/application-test.properties
index 1c1f727aa67b7064277c67b3ca0f8c263666270e..f0ff122cfa3a826aebe07af30283ecb4ea952b92 100644
--- a/src/test/resources/application-test.properties
+++ b/src/test/resources/application-test.properties
@@ -18,7 +18,8 @@
 
 spring.profiles.active=test
 
-spring.jpa.hibernate.ddl-auto=create
-spring.liquibase.enabled=false
+spring.jpa.hibernate.ddl-auto=validate
+spring.liquibase.enabled=true
 
 spring.datasource.url=jdbc:h2:mem:testdb
+spring.liquibase.change-log=classpath:/changelog-test.yaml
diff --git a/src/test/resources/application.yaml b/src/test/resources/application.yaml
index 3661995b1cc3e6d9bd04cb9228c38a5372ca1147..206bf8aa8b582ad45da4ba7b0774d16c27c7aa26 100644
--- a/src/test/resources/application.yaml
+++ b/src/test/resources/application.yaml
@@ -36,9 +36,6 @@ submit:
     person-timeout: 60
 
 spring:
-  liquibase:
-    change-log: "classpath:/changelog-master.yaml"
-
   h2:
     console:
       enabled: true
diff --git a/src/test/resources/changelog-test-db.yaml b/src/test/resources/changelog-test-db.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..c2b2e43ce24df447c4fe244e444ddf0a54c07633
--- /dev/null
+++ b/src/test/resources/changelog-test-db.yaml
@@ -0,0 +1,30 @@
+#
+# Submit
+# Copyright (C) 2020 - Delft University of Technology
+#
+# 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 <https://www.gnu.org/licenses/>.
+#
+
+databaseChangeLog:
+  - include:
+      file: "table-setup.yaml"
+  - include:
+      file: "migrations.yaml"
+  - changeSet:
+      id: populate-sequence
+      author: ruben
+      changes:
+        - sql: INSERT INTO hibernate_sequence (next_val) VALUES (0);
+
+
diff --git a/src/test/resources/changelog-test.yaml b/src/test/resources/changelog-test.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..cedd93e79a0cc45f6218f29fe852cfdb6d177dd3
--- /dev/null
+++ b/src/test/resources/changelog-test.yaml
@@ -0,0 +1,35 @@
+#
+# Submit
+# Copyright (C) 2020 - Delft University of Technology
+#
+# 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 <https://www.gnu.org/licenses/>.
+#
+
+databaseChangeLog:
+  - include:
+      file: "table-setup.yaml"
+  - include:
+      file: "migrations.yaml"
+#  - changeSet:
+#      id: drop-sequence-table
+#      author: ruben
+#      changes:
+#        - dropTable:
+#            tableName: hibernate_sequence
+  - changeSet:
+      id: create-sequence
+      author: ruben
+      changes:
+        - createSequence:
+            sequenceName: hibernate_sequence