diff --git a/webapp/.angular-cli.json b/webapp/.angular-cli.json
index 862071be9ffc02a136894ecaed8fe4050f4c44fe..8310aa96c05fa2afd99b235933f74649a667ee41 100644
--- a/webapp/.angular-cli.json
+++ b/webapp/.angular-cli.json
@@ -19,7 +19,8 @@
       "testTsconfig": "tsconfig.spec.json",
       "prefix": "app",
       "styles": [
-        "styles.scss"
+        "styles.scss",
+        "theme.scss"
       ],
       "scripts": [],
       "environmentSource": "environments/environment.ts",
diff --git a/webapp/package-lock.json b/webapp/package-lock.json
index 9ba900984a9e075ac4bf541806e54c3f1e1a29dd..090f6bcac4ad711bad57fa74369b6848f681d9e1 100644
--- a/webapp/package-lock.json
+++ b/webapp/package-lock.json
@@ -9167,6 +9167,12 @@
         "pify": "3.0.0"
       }
     },
+    "sass-recursive-map-merge": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/sass-recursive-map-merge/-/sass-recursive-map-merge-1.0.1.tgz",
+      "integrity": "sha512-OuDTGVGx2o2sPeaSgGob5s2Qf6LxoMU4LG7n6vCzNgfXyBz/y8tKzcEYdmvgyhjvGQVcGA1g2UJnP7WMmahuVg==",
+      "dev": true
+    },
     "saucelabs": {
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/saucelabs/-/saucelabs-1.3.0.tgz",
diff --git a/webapp/package.json b/webapp/package.json
index 20789ef7cb18019d6c09e0ff6f44ecc8c75d6f64..01a565070f427b258d33759fe79a2b959f4ed199 100644
--- a/webapp/package.json
+++ b/webapp/package.json
@@ -45,6 +45,7 @@
     "protractor": "~5.1.2",
     "ts-node": "~4.1.0",
     "tslint": "~5.9.1",
+    "sass-recursive-map-merge": "^1.0.1",
     "typescript": "~2.5.3"
   }
 }
diff --git a/webapp/src/app/app-routing.module.ts b/webapp/src/app/app-routing.module.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5c38405b0235287c98d52810c8165c4cf5052902
--- /dev/null
+++ b/webapp/src/app/app-routing.module.ts
@@ -0,0 +1,12 @@
+import { NgModule } from '@angular/core';
+import { Routes, RouterModule } from '@angular/router';
+
+const routes: Routes = [
+  { path: '', redirectTo: 'app', pathMatch: 'full' }
+];
+
+@NgModule({
+  imports: [RouterModule.forRoot(routes)],
+  exports: [RouterModule]
+})
+export class AppRoutingModule { }
diff --git a/webapp/src/app/app.component.html b/webapp/src/app/app.component.html
index fa2706a406ba65e05f8e23003ce6e9df291fa146..6a164d6971c1e767eae999e03e288a6325ba9ed9 100644
--- a/webapp/src/app/app.component.html
+++ b/webapp/src/app/app.component.html
@@ -1,20 +1,2 @@
-<!--The content below is only a placeholder and can be replaced.-->
-<div style="text-align:center">
-  <h1>
-    Welcome to {{ title }}!
-  </h1>
-  <img width="300" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==">
-</div>
-<h2>Here are some links to help you start: </h2>
-<ul>
-  <li>
-    <h2><a target="_blank" rel="noopener" href="https://angular.io/tutorial">Tour of Heroes</a></h2>
-  </li>
-  <li>
-    <h2><a target="_blank" rel="noopener" href="https://github.com/angular/angular-cli/wiki">CLI Documentation</a></h2>
-  </li>
-  <li>
-    <h2><a target="_blank" rel="noopener" href="https://blog.angular.io/">Angular blog</a></h2>
-  </li>
-</ul>
+<router-outlet></router-outlet>
 
diff --git a/webapp/src/app/app.module.ts b/webapp/src/app/app.module.ts
index f4b848b8835c0e14215239eab4444bcca16d2493..2d9e72fbd4c9f4672bcaa7c9a0c8be27abbd72a3 100644
--- a/webapp/src/app/app.module.ts
+++ b/webapp/src/app/app.module.ts
@@ -4,8 +4,9 @@ import { NgModule } from '@angular/core';
 
 import { AppMaterialModule } from './app.material.module';
 
-
 import { AppComponent } from './app.component';
+import { AppRoutingModule } from './app-routing.module';
+import { CoreModule } from './core/core.module';
 
 
 @NgModule({
@@ -15,7 +16,9 @@ import { AppComponent } from './app.component';
   imports: [
     BrowserModule,
     BrowserAnimationsModule,
-    AppMaterialModule
+    AppMaterialModule,
+    CoreModule,
+    AppRoutingModule
   ],
   providers: [],
   bootstrap: [AppComponent]
diff --git a/webapp/src/app/core/components/footer/footer.component.html b/webapp/src/app/core/components/footer/footer.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..6800e0eb1bdd02470a1e7a55dab3a9ed7d615d50
--- /dev/null
+++ b/webapp/src/app/core/components/footer/footer.component.html
@@ -0,0 +1,3 @@
+<p>
+  footer works!
+</p>
diff --git a/webapp/src/app/core/components/footer/footer.component.scss b/webapp/src/app/core/components/footer/footer.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/webapp/src/app/core/components/footer/footer.component.spec.ts b/webapp/src/app/core/components/footer/footer.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2ca6c45431d529bbf993d17231e1c7604bb6a480
--- /dev/null
+++ b/webapp/src/app/core/components/footer/footer.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { FooterComponent } from './footer.component';
+
+describe('FooterComponent', () => {
+  let component: FooterComponent;
+  let fixture: ComponentFixture<FooterComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ FooterComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(FooterComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/webapp/src/app/core/components/footer/footer.component.ts b/webapp/src/app/core/components/footer/footer.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..da17d824231ebb84b3dd5872e8ad33b5ef659771
--- /dev/null
+++ b/webapp/src/app/core/components/footer/footer.component.ts
@@ -0,0 +1,15 @@
+import { Component, OnInit } from '@angular/core';
+
+@Component({
+  selector: 'app-footer',
+  templateUrl: './footer.component.html',
+  styleUrls: ['./footer.component.scss']
+})
+export class FooterComponent implements OnInit {
+
+  constructor() { }
+
+  ngOnInit() {
+  }
+
+}
diff --git a/webapp/src/app/core/components/header/header.component.html b/webapp/src/app/core/components/header/header.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..c3dba65821136f45183b547d8cc500d8b27f793a
--- /dev/null
+++ b/webapp/src/app/core/components/header/header.component.html
@@ -0,0 +1,3 @@
+<mat-toolbar color="primary">
+  <span>OpenData</span>
+</mat-toolbar>
\ No newline at end of file
diff --git a/webapp/src/app/core/components/header/header.component.scss b/webapp/src/app/core/components/header/header.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/webapp/src/app/core/components/header/header.component.spec.ts b/webapp/src/app/core/components/header/header.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2d0479d7d071a5e6d19a74d195ff34b2edeb5616
--- /dev/null
+++ b/webapp/src/app/core/components/header/header.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { HeaderComponent } from './header.component';
+
+describe('HeaderComponent', () => {
+  let component: HeaderComponent;
+  let fixture: ComponentFixture<HeaderComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ HeaderComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(HeaderComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/webapp/src/app/core/components/header/header.component.ts b/webapp/src/app/core/components/header/header.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..591e148a7dc2965fb26c07394dd0ae005930bca8
--- /dev/null
+++ b/webapp/src/app/core/components/header/header.component.ts
@@ -0,0 +1,15 @@
+import { Component, OnInit } from '@angular/core';
+
+@Component({
+  selector: 'app-header',
+  templateUrl: './header.component.html',
+  styleUrls: ['./header.component.scss']
+})
+export class HeaderComponent implements OnInit {
+
+  constructor() { }
+
+  ngOnInit() {
+  }
+
+}
diff --git a/webapp/src/app/core/components/main/main.component.html b/webapp/src/app/core/components/main/main.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..bfa5088f191e9dc19dae414a23c66f29c10a30fa
--- /dev/null
+++ b/webapp/src/app/core/components/main/main.component.html
@@ -0,0 +1,9 @@
+<app-header></app-header>
+
+<div class="container-fluid">
+  <p>
+    main works!
+  </p>
+</div>
+
+<app-footer></app-footer>
diff --git a/webapp/src/app/core/components/main/main.component.scss b/webapp/src/app/core/components/main/main.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/webapp/src/app/core/components/main/main.component.spec.ts b/webapp/src/app/core/components/main/main.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0878044ab32aff4c5da064c511d97805bf305c08
--- /dev/null
+++ b/webapp/src/app/core/components/main/main.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { MainComponent } from './main.component';
+
+describe('MainComponent', () => {
+  let component: MainComponent;
+  let fixture: ComponentFixture<MainComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ MainComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(MainComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/webapp/src/app/core/components/main/main.component.ts b/webapp/src/app/core/components/main/main.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8b899ba8f85ff4b77f58fe4b40e901e358650a85
--- /dev/null
+++ b/webapp/src/app/core/components/main/main.component.ts
@@ -0,0 +1,15 @@
+import { Component, OnInit } from '@angular/core';
+
+@Component({
+  selector: 'app-main',
+  templateUrl: './main.component.html',
+  styleUrls: ['./main.component.scss']
+})
+export class MainComponent implements OnInit {
+
+  constructor() { }
+
+  ngOnInit() {
+  }
+
+}
diff --git a/webapp/src/app/core/core-routing.module.ts b/webapp/src/app/core/core-routing.module.ts
new file mode 100644
index 0000000000000000000000000000000000000000..90928dee8fee6426218ddb728ebd039c707db39e
--- /dev/null
+++ b/webapp/src/app/core/core-routing.module.ts
@@ -0,0 +1,13 @@
+import { NgModule } from '@angular/core';
+import { Routes, RouterModule } from '@angular/router';
+import { MainComponent } from './components/main/main.component';
+
+const routes: Routes = [
+  { path: 'app', component: MainComponent }
+];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule]
+})
+export class CoreRoutingModule { }
diff --git a/webapp/src/app/core/core.module.ts b/webapp/src/app/core/core.module.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e7e457442d22a2eb7963e5ac31f083260f1817aa
--- /dev/null
+++ b/webapp/src/app/core/core.module.ts
@@ -0,0 +1,18 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+
+import { CoreRoutingModule } from './core-routing.module';
+import { HeaderComponent } from './components/header/header.component';
+import { MainComponent } from './components/main/main.component';
+import { FooterComponent } from './components/footer/footer.component';
+import { AppMaterialModule } from '../app.material.module';
+
+@NgModule({
+  imports: [
+    CommonModule,
+    CoreRoutingModule,
+    AppMaterialModule
+  ],
+  declarations: [HeaderComponent, MainComponent, FooterComponent]
+})
+export class CoreModule { }
diff --git a/webapp/src/assets/img/android-chrome-192x192.png b/webapp/src/assets/img/android-chrome-192x192.png
new file mode 100644
index 0000000000000000000000000000000000000000..27c7e19579c49567b1de0625ddf670927b0718e7
Binary files /dev/null and b/webapp/src/assets/img/android-chrome-192x192.png differ
diff --git a/webapp/src/assets/img/android-chrome-512x512.png b/webapp/src/assets/img/android-chrome-512x512.png
new file mode 100644
index 0000000000000000000000000000000000000000..70d2e5c65b6f2d949814afaefbf7dfa0521687ea
Binary files /dev/null and b/webapp/src/assets/img/android-chrome-512x512.png differ
diff --git a/webapp/src/assets/img/apple-touch-icon.png b/webapp/src/assets/img/apple-touch-icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..f6a9f6786a18ae9941594b9731ad679fe919be15
Binary files /dev/null and b/webapp/src/assets/img/apple-touch-icon.png differ
diff --git a/webapp/src/assets/img/favicon-16x16.png b/webapp/src/assets/img/favicon-16x16.png
new file mode 100644
index 0000000000000000000000000000000000000000..1870e1d32ea23a1106918dc041f86739f2589f61
Binary files /dev/null and b/webapp/src/assets/img/favicon-16x16.png differ
diff --git a/webapp/src/assets/img/favicon-32x32.png b/webapp/src/assets/img/favicon-32x32.png
new file mode 100644
index 0000000000000000000000000000000000000000..f6297745e4fddbc6d05d33642f5fbd858f1cbef6
Binary files /dev/null and b/webapp/src/assets/img/favicon-32x32.png differ
diff --git a/webapp/src/assets/img/favicon.ico b/webapp/src/assets/img/favicon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..5619a8f1cf3db720725e00d80636e23de406e8de
Binary files /dev/null and b/webapp/src/assets/img/favicon.ico differ
diff --git a/webapp/src/assets/img/logo.svg b/webapp/src/assets/img/logo.svg
new file mode 100644
index 0000000000000000000000000000000000000000..2bb541a68f8595a52f51e4c22fe9f69e65b6b97a
--- /dev/null
+++ b/webapp/src/assets/img/logo.svg
@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="185.6" height="37.9" viewBox="0 0 185.6 37.9">
+  <path d="M14.4 21.3C12.8 23 10.5 24 8.1 24c-2.2 0-4.4-.9-5.9-2.4C0 19.5 0 17.4 0 12c0-5.3 0-7.4 2.2-9.6C3.8.9 5.9 0 8.1 0c4.2 0 7.4 2.7 8.1 6.9h-1.8c-.7-3.3-3.1-5.3-6.3-5.3-1.7 0-3.3.6-4.5 1.8C1.9 5.1 1.8 6.9 1.8 12s.1 6.9 1.8 8.6c1.2 1.2 2.8 1.8 4.5 1.8 1.9 0 3.7-.8 4.9-2.3 1-1.3 1.4-2.7 1.4-4.7v-2.1H8.1v-1.6h8.2v3.8c0 2.6-.5 4.3-1.9 5.8zm22.2 2.5l-5.7-10.9h-6.5v10.9h-1.8V.2h9c3.9 0 6.8 2.2 6.8 6.3 0 3.5-2.1 5.8-5.5 6.4l5.7 10.9h-2zm-5.2-22h-7v9.6h7c3 0 5.2-1.5 5.2-4.8s-2.2-4.8-5.2-4.8m27 22l-2.1-5.7h-11l-2.1 5.7h-1.9L50 .2h1.6l8.7 23.6h-1.9zM50.8 2.6l-5 13.9h9.9L50.8 2.6zM80 23.8L66.3 3.3v20.5h-1.8V.2h1.8L80 20.7V.2h1.8v23.6H80zm23-2.1c-1.4 1.4-3.5 2.1-5.9 2.1h-8V.2h8c2.5 0 4.5.8 5.9 2.2 2.4 2.4 2.3 6.1 2.3 9.3 0 3.2.1 7.6-2.3 10m-1.2-18.1c-1.4-1.4-3.2-1.8-5.1-1.8H91v20.4h5.7c1.9 0 3.7-.4 5.1-1.8 1.9-1.9 1.8-6.1 1.8-8.7-.1-2.6.1-6.2-1.8-8.1z" fill="#ed1c24"/>
+  <path d="M110.8 23.8V.2h4.6v19.5h10.8v4.1h-15.4zm25.6-9.7v9.7h-4.6v-9.7L124.7.2h5l4.4 9.5 4.4-9.5h5l-7.1 13.9zm24 7.3c-1.7 1.7-3.7 2.6-6.4 2.6s-4.8-.9-6.4-2.6c-2.4-2.4-2.3-5.3-2.3-9.4 0-4.1-.1-7 2.3-9.4C149.3.9 151.3 0 154 0s4.7.9 6.4 2.6c2.4 2.4 2.4 5.3 2.4 9.4-.1 4.1 0 7.1-2.4 9.4m-3.5-16c-.7-.8-1.7-1.3-3-1.3s-2.3.5-3 1.3c-.9 1-1.1 2.1-1.1 6.6s.2 5.6 1.1 6.6c.7.8 1.7 1.3 3 1.3s2.3-.5 3-1.3c.9-1 1.2-2.1 1.2-6.6s-.3-5.6-1.2-6.6zm24.6 18.4l-9.4-14.5v14.5h-4.6V.2h4.1l9.4 14.5V.2h4.6v23.6h-4.1z" fill="#050607"/>
+  <path d="M159.4 33.2c0-.8 0-2-1.1-2s-1 1.5-1 2.3c0 .7.1 1.8 1 1.8 1 0 1.1-.7 1.1-2.1m-2-1.9c.3-.5.7-.7 1.2-.7.9 0 1.7.3 1.7 2.6 0 1.3 0 2.8-1.7 2.8-.5 0-.9-.2-1.2-.6v2.5h-.9v-7.3h.9v.7zm-61.1 3.1c0 .5.2 1 .8 1 .5 0 1.2-.3 1.1-2-.8-.1-1.9-.1-1.9 1m1.9.8c-.3.6-.7.9-1.3.9-1.1 0-1.4-.7-1.4-1.7 0-1.6 1.6-1.7 2.8-1.6 0-.7 0-1.5-.9-1.5-.6 0-.9.4-.8.9h-.9c0-1.2.7-1.6 1.8-1.6 1.4 0 1.7.7 1.7 1.6v2.5c0 .4 0 .8.1 1.3h-.9l-.2-.8zm15.4.7v-3.7c0-.5-.2-1-.8-1-.3 0-.6.2-.7.4-.2.3-.2.6-.2.8V36h-.9v-4.2c0-.4 0-.7-.1-1.1h.9v.7c.2-.6.7-.8 1.3-.8.5 0 1 .2 1.2.7.3-.6.8-.7 1.3-.7.6 0 1.4.1 1.4 1.4v4h-.9v-3.7c0-.5-.2-1-.8-1-.3 0-.4 0-.7.2-.2.2-.3.7-.3.9V36c.2-.1-.7-.1-.7-.1zm12.1-7.5l-1.4 1.5h-.7l.9-1.5h1.2zm-.2 4.5c0-1.5-.4-1.7-1.1-1.7-.6 0-1 .3-1 1.7h2.1zm-2.1.7c0 1.5.4 1.8 1.1 1.8.6 0 .9-.5.9-1h.9c0 1.2-.7 1.7-1.8 1.7s-2-.3-2-2.7c0-1.5.2-2.8 2-2.8 1.5 0 1.9.8 1.9 2.6v.4h-3zm8.8-2.9v-1l.9-.4v1.4h1.1v.6H133v3.2c0 .3 0 .8.8.8h.4v.7c-.3 0-.6.1-.9.1-.8 0-1.2-.3-1.2-.9v-3.7h-.9v-.6l1-.2zm8.4.8c.4-.8.8-.9 1.7-.9v.9h-.4c-.9 0-1.2.7-1.2 1.4V36h-.9v-5.3h.9l-.1.8zm9.7 1.5c0-1.4-.4-1.8-1.1-1.8-.7 0-1.1.3-1.1 1.8 0 1.7.2 2.3 1.1 2.3.8 0 1.1-.6 1.1-2.3m-3.1.4c0-1.5.2-2.8 2-2.8s2 1.3 2 2.8c0 2.3-.9 2.7-2 2.7-1.2-.1-2-.4-2-2.7zm21.5-.4c0-1.4-.4-1.8-1.1-1.8s-1.1.3-1.1 1.8c0 1.7.2 2.3 1.1 2.3.8 0 1.1-.6 1.1-2.3m-3.1.4c0-1.5.2-2.8 2-2.8s2 1.3 2 2.8c0 2.3-.9 2.7-2 2.7-1.2-.1-2-.4-2-2.7zm10.4 2.5h-.9v-7.5h.9v7.5zm8.4-3c0-1.5-.4-1.7-1.1-1.7-.5 0-1 .3-1 1.7h2.1zm-2.1.7c0 1.5.4 1.8 1.1 1.8.6 0 .9-.5.9-1h.9c0 1.2-.7 1.7-1.8 1.7s-2-.3-2-2.7c0-1.5.2-2.8 2-2.8 1.5 0 1.9.8 1.9 2.6v.4h-3zm-91.8 2.3h-.9v-7.5h.9v7.5z" fill="#231f20"/>
+</svg>
diff --git a/webapp/src/scss/_functions.scss b/webapp/src/scss/_functions.scss
new file mode 100644
index 0000000000000000000000000000000000000000..1266d34bd9029922e92809b52827b479c7d9e8f9
--- /dev/null
+++ b/webapp/src/scss/_functions.scss
@@ -0,0 +1,86 @@
+// Bootstrap functions
+//
+// Utility mixins and functions for evalutating source code across our variables, maps, and mixins.
+
+// Ascending
+// Used to evaluate Sass maps like our grid breakpoints.
+@mixin _assert-ascending($map, $map-name) {
+  $prev-key: null;
+  $prev-num: null;
+  @each $key, $num in $map {
+    @if $prev-num == null {
+      // Do nothing
+    } @else if not comparable($prev-num, $num) {
+      @warn "Potentially invalid value for #{$map-name}: This map must be in ascending order, but key '#{$key}' has value #{$num} whose unit makes it incomparable to #{$prev-num}, the value of the previous key '#{$prev-key}' !";
+    } @else if $prev-num >= $num {
+      @warn "Invalid value for #{$map-name}: This map must be in ascending order, but key '#{$key}' has value #{$num} which isn't greater than #{$prev-num}, the value of the previous key '#{$prev-key}' !";
+    }
+    $prev-key: $key;
+    $prev-num: $num;
+  }
+}
+
+// Starts at zero
+// Another grid mixin that ensures the min-width of the lowest breakpoint starts at 0.
+@mixin _assert-starts-at-zero($map) {
+  $values: map-values($map);
+  $first-value: nth($values, 1);
+  @if $first-value != 0 {
+    @warn "First breakpoint in `$grid-breakpoints` must start at 0, but starts at #{$first-value}.";
+  }
+}
+
+// Replace `$search` with `$replace` in `$string`
+// Used on our SVG icon backgrounds for custom forms.
+//
+// @author Hugo Giraudel
+// @param {String} $string - Initial string
+// @param {String} $search - Substring to replace
+// @param {String} $replace ('') - New value
+// @return {String} - Updated string
+@function str-replace($string, $search, $replace: "") {
+  $index: str-index($string, $search);
+
+  @if $index {
+    @return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
+  }
+
+  @return $string;
+}
+
+// Color contrast
+@function color-yiq($color) {
+  $r: red($color);
+  $g: green($color);
+  $b: blue($color);
+
+  $yiq: (($r * 299) + ($g * 587) + ($b * 114)) / 1000;
+
+  @if ($yiq >= $yiq-contrasted-threshold) {
+    @return $yiq-text-dark;
+  } @else {
+    @return $yiq-text-light;
+  }
+}
+
+// Retrieve color Sass maps
+@function color($key: "blue") {
+  @return map-get($colors, $key);
+}
+
+@function theme-color($key: "primary") {
+  @return map-get($theme-colors, $key);
+}
+
+@function gray($key: "100") {
+  @return map-get($grays, $key);
+}
+
+// Request a theme color level
+@function theme-color-level($color-name: "primary", $level: 0) {
+  $color: theme-color($color-name);
+  $color-base: if($level > 0, #000, #fff);
+  $level: abs($level);
+
+  @return mix($color-base, $color, $level * $theme-color-interval);
+}
diff --git a/webapp/src/scss/_grid.scss b/webapp/src/scss/_grid.scss
new file mode 100644
index 0000000000000000000000000000000000000000..a227515379c4bd9979039bc0e86ca3626123b96b
--- /dev/null
+++ b/webapp/src/scss/_grid.scss
@@ -0,0 +1,52 @@
+// Container widths
+//
+// Set the container width, and override it for fixed navbars in media queries.
+
+@if $enable-grid-classes {
+  .container {
+    @include make-container();
+    @include make-container-max-widths();
+  }
+}
+
+// Fluid container
+//
+// Utilizes the mixin meant for fixed width containers, but with 100% width for
+// fluid, full width layouts.
+
+@if $enable-grid-classes {
+  .container-fluid {
+    @include make-container();
+  }
+}
+
+// Row
+//
+// Rows contain and clear the floats of your columns.
+
+@if $enable-grid-classes {
+  .row {
+    @include make-row();
+  }
+
+  // Remove the negative margin from default .row, then the horizontal padding
+  // from all immediate children columns (to prevent runaway style inheritance).
+  .no-gutters {
+    margin-right: 0;
+    margin-left: 0;
+
+    > .col,
+    > [class*="col-"] {
+      padding-right: 0;
+      padding-left: 0;
+    }
+  }
+}
+
+// Columns
+//
+// Common styles for small and large grid columns
+
+@if $enable-grid-classes {
+  @include make-grid-columns();
+}
diff --git a/webapp/src/scss/_variables.scss b/webapp/src/scss/_variables.scss
new file mode 100644
index 0000000000000000000000000000000000000000..be580deb334db623fe68d885df0823f6df945b87
--- /dev/null
+++ b/webapp/src/scss/_variables.scss
@@ -0,0 +1,894 @@
+// Variables
+//
+// Variables should follow the `$component-state-property-size` formula for
+// consistent naming. Ex: $nav-link-disabled-color and $modal-content-box-shadow-xs.
+
+
+//
+// Color system
+//
+
+// stylelint-disable
+$white:    #fff !default;
+$gray-100: #f8f9fa !default;
+$gray-200: #e9ecef !default;
+$gray-300: #dee2e6 !default;
+$gray-400: #ced4da !default;
+$gray-500: #adb5bd !default;
+$gray-600: #6c757d !default;
+$gray-700: #495057 !default;
+$gray-800: #343a40 !default;
+$gray-900: #212529 !default;
+$black:    #000 !default;
+
+$grays: () !default;
+$grays: map-merge((
+  "100": $gray-100,
+  "200": $gray-200,
+  "300": $gray-300,
+  "400": $gray-400,
+  "500": $gray-500,
+  "600": $gray-600,
+  "700": $gray-700,
+  "800": $gray-800,
+  "900": $gray-900
+), $grays);
+
+$blue:    #007bff !default;
+$indigo:  #6610f2 !default;
+$purple:  #6f42c1 !default;
+$pink:    #e83e8c !default;
+$red:     #dc3545 !default;
+$orange:  #fd7e14 !default;
+$yellow:  #ffc107 !default;
+$green:   #28a745 !default;
+$teal:    #20c997 !default;
+$cyan:    #17a2b8 !default;
+
+$colors: () !default;
+$colors: map-merge((
+  "blue":       $blue,
+  "indigo":     $indigo,
+  "purple":     $purple,
+  "pink":       $pink,
+  "red":        $red,
+  "orange":     $orange,
+  "yellow":     $yellow,
+  "green":      $green,
+  "teal":       $teal,
+  "cyan":       $cyan,
+  "white":      $white,
+  "gray":       $gray-600,
+  "gray-dark":  $gray-800
+), $colors);
+
+$primary:       $blue !default;
+$secondary:     $gray-600 !default;
+$success:       $green !default;
+$info:          $cyan !default;
+$warning:       $yellow !default;
+$danger:        $red !default;
+$light:         $gray-100 !default;
+$dark:          $gray-800 !default;
+
+$theme-colors: () !default;
+$theme-colors: map-merge((
+  "primary":    $primary,
+  "secondary":  $secondary,
+  "success":    $success,
+  "info":       $info,
+  "warning":    $warning,
+  "danger":     $danger,
+  "light":      $light,
+  "dark":       $dark
+), $theme-colors);
+// stylelint-enable
+
+// Set a specific jump point for requesting color jumps
+$theme-color-interval:      8% !default;
+
+// The yiq lightness value that determines when the lightness of color changes from "dark" to "light". Acceptable values are between 0 and 255.
+$yiq-contrasted-threshold: 150 !default;
+
+// Customize the light and dark text colors for use in our YIQ color contrast function.
+$yiq-text-dark: $gray-900 !default;
+$yiq-text-light: $white !default;
+
+// Options
+//
+// Quickly modify global styling by enabling or disabling optional features.
+
+$enable-caret:              true !default;
+$enable-rounded:            true !default;
+$enable-shadows:            false !default;
+$enable-gradients:          false !default;
+$enable-transitions:        true !default;
+$enable-hover-media-query:  false !default; // Deprecated, no longer affects any compiled CSS
+$enable-grid-classes:       true !default;
+$enable-print-styles:       true !default;
+
+
+// Spacing
+//
+// Control the default styling of most Bootstrap elements by modifying these
+// variables. Mostly focused on spacing.
+// You can add more entries to the $spacers map, should you need more variation.
+
+// stylelint-disable
+$spacer: 1rem !default;
+$spacers: () !default;
+$spacers: map-merge((
+  0: 0,
+  1: ($spacer * .25),
+  2: ($spacer * .5),
+  3: $spacer,
+  4: ($spacer * 1.5),
+  5: ($spacer * 3)
+), $spacers);
+
+// This variable affects the `.h-*` and `.w-*` classes.
+$sizes: () !default;
+$sizes: map-merge((
+  25: 25%,
+  50: 50%,
+  75: 75%,
+  100: 100%
+), $sizes);
+// stylelint-enable
+
+// Body
+//
+// Settings for the `<body>` element.
+
+$body-bg:                   $white !default;
+$body-color:                $gray-900 !default;
+
+// Links
+//
+// Style anchor elements.
+
+$link-color:                theme-color("primary") !default;
+$link-decoration:           none !default;
+$link-hover-color:          darken($link-color, 15%) !default;
+$link-hover-decoration:     underline !default;
+
+// Paragraphs
+//
+// Style p element.
+
+$paragraph-margin-bottom:   1rem !default;
+
+
+// Grid breakpoints
+//
+// Define the minimum dimensions at which your layout will change,
+// adapting to different screen sizes, for use in media queries.
+
+$grid-breakpoints: (
+  xs: 0,
+  sm: 576px,
+  md: 768px,
+  lg: 992px,
+  xl: 1200px
+) !default;
+
+@include _assert-ascending($grid-breakpoints, "$grid-breakpoints");
+@include _assert-starts-at-zero($grid-breakpoints);
+
+
+// Grid containers
+//
+// Define the maximum width of `.container` for different screen sizes.
+
+$container-max-widths: (
+  sm: 540px,
+  md: 720px,
+  lg: 960px,
+  xl: 1140px
+) !default;
+
+@include _assert-ascending($container-max-widths, "$container-max-widths");
+
+
+// Grid columns
+//
+// Set the number of columns and specify the width of the gutters.
+
+$grid-columns:                12 !default;
+$grid-gutter-width:           30px !default;
+
+// Components
+//
+// Define common padding and border radius sizes and more.
+
+$line-height-lg:              1.5 !default;
+$line-height-sm:              1.5 !default;
+
+$border-width:                1px !default;
+$border-color:                $gray-300 !default;
+
+$border-radius:               .25rem !default;
+$border-radius-lg:            .3rem !default;
+$border-radius-sm:            .2rem !default;
+
+$component-active-color:      $white !default;
+$component-active-bg:         theme-color("primary") !default;
+
+$caret-width:                 .3em !default;
+
+$transition-base:             all .2s ease-in-out !default;
+$transition-fade:             opacity .15s linear !default;
+$transition-collapse:         height .35s ease !default;
+
+
+// Fonts
+//
+// Font, line-height, and color for body text, headings, and more.
+
+// stylelint-disable value-keyword-case
+$font-family-sans-serif:      -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol" !default;
+$font-family-monospace:       SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !default;
+$font-family-base:            $font-family-sans-serif !default;
+// stylelint-enable value-keyword-case
+
+$font-size-base:              1rem !default; // Assumes the browser default, typically `16px`
+$font-size-lg:                ($font-size-base * 1.25) !default;
+$font-size-sm:                ($font-size-base * .875) !default;
+
+$font-weight-light:           300 !default;
+$font-weight-normal:          400 !default;
+$font-weight-bold:            700 !default;
+
+$font-weight-base:            $font-weight-normal !default;
+$line-height-base:            1.5 !default;
+
+$h1-font-size:                $font-size-base * 2.5 !default;
+$h2-font-size:                $font-size-base * 2 !default;
+$h3-font-size:                $font-size-base * 1.75 !default;
+$h4-font-size:                $font-size-base * 1.5 !default;
+$h5-font-size:                $font-size-base * 1.25 !default;
+$h6-font-size:                $font-size-base !default;
+
+$headings-margin-bottom:      ($spacer / 2) !default;
+$headings-font-family:        inherit !default;
+$headings-font-weight:        500 !default;
+$headings-line-height:        1.2 !default;
+$headings-color:              inherit !default;
+
+$display1-size:               6rem !default;
+$display2-size:               5.5rem !default;
+$display3-size:               4.5rem !default;
+$display4-size:               3.5rem !default;
+
+$display1-weight:             300 !default;
+$display2-weight:             300 !default;
+$display3-weight:             300 !default;
+$display4-weight:             300 !default;
+$display-line-height:         $headings-line-height !default;
+
+$lead-font-size:              ($font-size-base * 1.25) !default;
+$lead-font-weight:            300 !default;
+
+$small-font-size:             80% !default;
+
+$text-muted:                  $gray-600 !default;
+
+$blockquote-small-color:      $gray-600 !default;
+$blockquote-font-size:        ($font-size-base * 1.25) !default;
+
+$hr-border-color:             rgba($black, .1) !default;
+$hr-border-width:             $border-width !default;
+
+$mark-padding:                .2em !default;
+
+$dt-font-weight:              $font-weight-bold !default;
+
+$kbd-box-shadow:              inset 0 -.1rem 0 rgba($black, .25) !default;
+$nested-kbd-font-weight:      $font-weight-bold !default;
+
+$list-inline-padding:         .5rem !default;
+
+$mark-bg:                     #fcf8e3 !default;
+
+$hr-margin-y:                 $spacer !default;
+
+
+// Tables
+//
+// Customizes the `.table` component with basic values, each used across all table variations.
+
+$table-cell-padding:          .75rem !default;
+$table-cell-padding-sm:       .3rem !default;
+
+$table-bg:                    transparent !default;
+$table-accent-bg:             rgba($black, .05) !default;
+$table-hover-bg:              rgba($black, .075) !default;
+$table-active-bg:             $table-hover-bg !default;
+
+$table-border-width:          $border-width !default;
+$table-border-color:          $gray-300 !default;
+
+$table-head-bg:               $gray-200 !default;
+$table-head-color:            $gray-700 !default;
+
+$table-dark-bg:               $gray-900 !default;
+$table-dark-accent-bg:        rgba($white, .05) !default;
+$table-dark-hover-bg:         rgba($white, .075) !default;
+$table-dark-border-color:     lighten($gray-900, 7.5%) !default;
+$table-dark-color:            $body-bg !default;
+
+
+// Buttons + Forms
+//
+// Shared variables that are reassigned to `$input-` and `$btn-` specific variables.
+
+$input-btn-padding-y:         .375rem !default;
+$input-btn-padding-x:         .75rem !default;
+$input-btn-line-height:       $line-height-base !default;
+
+$input-btn-focus-width:       .2rem !default;
+$input-btn-focus-color:       rgba($component-active-bg, .25) !default;
+$input-btn-focus-box-shadow:  0 0 0 $input-btn-focus-width $input-btn-focus-color !default;
+
+$input-btn-padding-y-sm:      .25rem !default;
+$input-btn-padding-x-sm:      .5rem !default;
+$input-btn-line-height-sm:    $line-height-sm !default;
+
+$input-btn-padding-y-lg:      .5rem !default;
+$input-btn-padding-x-lg:      1rem !default;
+$input-btn-line-height-lg:    $line-height-lg !default;
+
+$input-btn-border-width:      $border-width !default;
+
+
+// Buttons
+//
+// For each of Bootstrap's buttons, define text, background, and border color.
+
+$btn-padding-y:               $input-btn-padding-y !default;
+$btn-padding-x:               $input-btn-padding-x !default;
+$btn-line-height:             $input-btn-line-height !default;
+
+$btn-padding-y-sm:            $input-btn-padding-y-sm !default;
+$btn-padding-x-sm:            $input-btn-padding-x-sm !default;
+$btn-line-height-sm:          $input-btn-line-height-sm !default;
+
+$btn-padding-y-lg:            $input-btn-padding-y-lg !default;
+$btn-padding-x-lg:            $input-btn-padding-x-lg !default;
+$btn-line-height-lg:          $input-btn-line-height-lg !default;
+
+$btn-border-width:            $input-btn-border-width !default;
+
+$btn-font-weight:             $font-weight-normal !default;
+$btn-box-shadow:              inset 0 1px 0 rgba($white, .15), 0 1px 1px rgba($black, .075) !default;
+$btn-focus-width:             $input-btn-focus-width !default;
+$btn-focus-box-shadow:        $input-btn-focus-box-shadow !default;
+$btn-disabled-opacity:        .65 !default;
+$btn-active-box-shadow:       inset 0 3px 5px rgba($black, .125) !default;
+
+$btn-link-disabled-color:     $gray-600 !default;
+
+$btn-block-spacing-y:         .5rem !default;
+
+// Allows for customizing button radius independently from global border radius
+$btn-border-radius:           $border-radius !default;
+$btn-border-radius-lg:        $border-radius-lg !default;
+$btn-border-radius-sm:        $border-radius-sm !default;
+
+$btn-transition:              color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out !default;
+
+
+// Forms
+
+$input-padding-y:                       $input-btn-padding-y !default;
+$input-padding-x:                       $input-btn-padding-x !default;
+$input-line-height:                     $input-btn-line-height !default;
+
+$input-padding-y-sm:                    $input-btn-padding-y-sm !default;
+$input-padding-x-sm:                    $input-btn-padding-x-sm !default;
+$input-line-height-sm:                  $input-btn-line-height-sm !default;
+
+$input-padding-y-lg:                    $input-btn-padding-y-lg !default;
+$input-padding-x-lg:                    $input-btn-padding-x-lg !default;
+$input-line-height-lg:                  $input-btn-line-height-lg !default;
+
+$input-bg:                              $white !default;
+$input-disabled-bg:                     $gray-200 !default;
+
+$input-color:                           $gray-700 !default;
+$input-border-color:                    $gray-400 !default;
+$input-border-width:                    $input-btn-border-width !default;
+$input-box-shadow:                      inset 0 1px 1px rgba($black, .075) !default;
+
+$input-border-radius:                   $border-radius !default;
+$input-border-radius-lg:                $border-radius-lg !default;
+$input-border-radius-sm:                $border-radius-sm !default;
+
+$input-focus-bg:                        $input-bg !default;
+$input-focus-border-color:              lighten($component-active-bg, 25%) !default;
+$input-focus-color:                     $input-color !default;
+$input-focus-width:                     $input-btn-focus-width !default;
+$input-focus-box-shadow:                $input-btn-focus-box-shadow !default;
+
+$input-placeholder-color:               $gray-600 !default;
+
+$input-height-border:                   $input-border-width * 2 !default;
+
+$input-height-inner:                    ($font-size-base * $input-btn-line-height) + ($input-btn-padding-y * 2) !default;
+$input-height:                          calc(#{$input-height-inner} + #{$input-height-border}) !default;
+
+$input-height-inner-sm:                 ($font-size-sm * $input-btn-line-height-sm) + ($input-btn-padding-y-sm * 2) !default;
+$input-height-sm:                       calc(#{$input-height-inner-sm} + #{$input-height-border}) !default;
+
+$input-height-inner-lg:                 ($font-size-lg * $input-btn-line-height-lg) + ($input-btn-padding-y-lg * 2) !default;
+$input-height-lg:                       calc(#{$input-height-inner-lg} + #{$input-height-border}) !default;
+
+$input-transition:                      border-color .15s ease-in-out, box-shadow .15s ease-in-out !default;
+
+$form-text-margin-top:                  .25rem !default;
+
+$form-check-input-gutter:               1.25rem !default;
+$form-check-input-margin-y:             .3rem !default;
+$form-check-input-margin-x:             .25rem !default;
+
+$form-check-inline-margin-x:            .75rem !default;
+$form-check-inline-input-margin-x:      .3125rem !default;
+
+$form-group-margin-bottom:              1rem !default;
+
+$input-group-addon-color:               $input-color !default;
+$input-group-addon-bg:                  $gray-200 !default;
+$input-group-addon-border-color:        $input-border-color !default;
+
+$custom-control-gutter:                 1.5rem !default;
+$custom-control-spacer-x:               1rem !default;
+
+$custom-control-indicator-size:         1rem !default;
+$custom-control-indicator-bg:           $gray-300 !default;
+$custom-control-indicator-bg-size:      50% 50% !default;
+$custom-control-indicator-box-shadow:   inset 0 .25rem .25rem rgba($black, .1) !default;
+
+$custom-control-indicator-disabled-bg:          $gray-200 !default;
+$custom-control-label-disabled-color:     $gray-600 !default;
+
+$custom-control-indicator-checked-color:        $component-active-color !default;
+$custom-control-indicator-checked-bg:           $component-active-bg !default;
+$custom-control-indicator-checked-disabled-bg:  rgba(theme-color("primary"), .5) !default;
+$custom-control-indicator-checked-box-shadow:   none !default;
+
+$custom-control-indicator-focus-box-shadow:     0 0 0 1px $body-bg, $input-btn-focus-box-shadow !default;
+
+$custom-control-indicator-active-color:         $component-active-color !default;
+$custom-control-indicator-active-bg:            lighten($component-active-bg, 35%) !default;
+$custom-control-indicator-active-box-shadow:    none !default;
+
+$custom-checkbox-indicator-border-radius:       $border-radius !default;
+$custom-checkbox-indicator-icon-checked:        str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='#{$custom-control-indicator-checked-color}' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3E%3C/svg%3E"), "#", "%23") !default;
+
+$custom-checkbox-indicator-indeterminate-bg:    $component-active-bg !default;
+$custom-checkbox-indicator-indeterminate-color: $custom-control-indicator-checked-color !default;
+$custom-checkbox-indicator-icon-indeterminate:  str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3E%3Cpath stroke='#{$custom-checkbox-indicator-indeterminate-color}' d='M0 2h4'/%3E%3C/svg%3E"), "#", "%23") !default;
+$custom-checkbox-indicator-indeterminate-box-shadow: none !default;
+
+$custom-radio-indicator-border-radius:          50% !default;
+$custom-radio-indicator-icon-checked:           str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='#{$custom-control-indicator-checked-color}'/%3E%3C/svg%3E"), "#", "%23") !default;
+
+$custom-select-padding-y:           .375rem !default;
+$custom-select-padding-x:          .75rem !default;
+$custom-select-height:              $input-height !default;
+$custom-select-indicator-padding:   1rem !default; // Extra padding to account for the presence of the background-image based indicator
+$custom-select-line-height:         $input-btn-line-height !default;
+$custom-select-color:               $input-color !default;
+$custom-select-disabled-color:      $gray-600 !default;
+$custom-select-bg:                  $white !default;
+$custom-select-disabled-bg:         $gray-200 !default;
+$custom-select-bg-size:             8px 10px !default; // In pixels because image dimensions
+$custom-select-indicator-color:     $gray-800 !default;
+$custom-select-indicator:           str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3E%3Cpath fill='#{$custom-select-indicator-color}' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E"), "#", "%23") !default;
+$custom-select-border-width:        $input-btn-border-width !default;
+$custom-select-border-color:        $input-border-color !default;
+$custom-select-border-radius:       $border-radius !default;
+
+$custom-select-focus-border-color:  $input-focus-border-color !default;
+$custom-select-focus-box-shadow:    inset 0 1px 2px rgba($black, .075), 0 0 5px rgba($custom-select-focus-border-color, .5) !default;
+
+$custom-select-font-size-sm:        75% !default;
+$custom-select-height-sm:           $input-height-sm !default;
+
+$custom-select-font-size-lg:        125% !default;
+$custom-select-height-lg:           $input-height-lg !default;
+
+$custom-file-height:                $input-height !default;
+$custom-file-focus-border-color:    $input-focus-border-color !default;
+$custom-file-focus-box-shadow:      $input-btn-focus-box-shadow !default;
+
+$custom-file-padding-y:             $input-btn-padding-y !default;
+$custom-file-padding-x:             $input-btn-padding-x !default;
+$custom-file-line-height:           $input-btn-line-height !default;
+$custom-file-color:                 $input-color !default;
+$custom-file-bg:                    $input-bg !default;
+$custom-file-border-width:          $input-btn-border-width !default;
+$custom-file-border-color:          $input-border-color !default;
+$custom-file-border-radius:         $input-border-radius !default;
+$custom-file-box-shadow:            $input-box-shadow !default;
+$custom-file-button-color:          $custom-file-color !default;
+$custom-file-button-bg:             $input-group-addon-bg !default;
+$custom-file-text: (
+  en: "Browse"
+) !default;
+
+
+// Form validation
+$form-feedback-margin-top:          $form-text-margin-top !default;
+$form-feedback-font-size:           $small-font-size !default;
+$form-feedback-valid-color:         theme-color("success") !default;
+$form-feedback-invalid-color:       theme-color("danger") !default;
+
+
+// Dropdowns
+//
+// Dropdown menu container and contents.
+
+$dropdown-min-width:                10rem !default;
+$dropdown-padding-y:                .5rem !default;
+$dropdown-spacer:                   .125rem !default;
+$dropdown-bg:                       $white !default;
+$dropdown-border-color:             rgba($black, .15) !default;
+$dropdown-border-radius:            $border-radius !default;
+$dropdown-border-width:             $border-width !default;
+$dropdown-divider-bg:               $gray-200 !default;
+$dropdown-box-shadow:               0 .5rem 1rem rgba($black, .175) !default;
+
+$dropdown-link-color:               $gray-900 !default;
+$dropdown-link-hover-color:         darken($gray-900, 5%) !default;
+$dropdown-link-hover-bg:            $gray-100 !default;
+
+$dropdown-link-active-color:        $component-active-color !default;
+$dropdown-link-active-bg:           $component-active-bg !default;
+
+$dropdown-link-disabled-color:      $gray-600 !default;
+
+$dropdown-item-padding-y:           .25rem !default;
+$dropdown-item-padding-x:           1.5rem !default;
+
+$dropdown-header-color:             $gray-600 !default;
+
+
+// Z-index master list
+//
+// Warning: Avoid customizing these values. They're used for a bird's eye view
+// of components dependent on the z-axis and are designed to all work together.
+
+$zindex-dropdown:                   1000 !default;
+$zindex-sticky:                     1020 !default;
+$zindex-fixed:                      1030 !default;
+$zindex-modal-backdrop:             1040 !default;
+$zindex-modal:                      1050 !default;
+$zindex-popover:                    1060 !default;
+$zindex-tooltip:                    1070 !default;
+
+// Navs
+
+$nav-link-padding-y:                .5rem !default;
+$nav-link-padding-x:                1rem !default;
+$nav-link-disabled-color:           $gray-600 !default;
+
+$nav-tabs-border-color:             $gray-300 !default;
+$nav-tabs-border-width:             $border-width !default;
+$nav-tabs-border-radius:            $border-radius !default;
+$nav-tabs-link-hover-border-color:  $gray-200 $gray-200 $nav-tabs-border-color !default;
+$nav-tabs-link-active-color:        $gray-700 !default;
+$nav-tabs-link-active-bg:           $body-bg !default;
+$nav-tabs-link-active-border-color: $gray-300 $gray-300 $nav-tabs-link-active-bg !default;
+
+$nav-pills-border-radius:           $border-radius !default;
+$nav-pills-link-active-color:       $component-active-color !default;
+$nav-pills-link-active-bg:          $component-active-bg !default;
+
+// Navbar
+
+$navbar-padding-y:                  ($spacer / 2) !default;
+$navbar-padding-x:                  $spacer !default;
+
+$navbar-nav-link-padding-x:         .5rem !default;
+
+$navbar-brand-font-size:            $font-size-lg !default;
+// Compute the navbar-brand padding-y so the navbar-brand will have the same height as navbar-text and nav-link
+$nav-link-height:                   ($font-size-base * $line-height-base + $nav-link-padding-y * 2) !default;
+$navbar-brand-height:               $navbar-brand-font-size * $line-height-base !default;
+$navbar-brand-padding-y:            ($nav-link-height - $navbar-brand-height) / 2 !default;
+
+$navbar-toggler-padding-y:          .25rem !default;
+$navbar-toggler-padding-x:          .75rem !default;
+$navbar-toggler-font-size:          $font-size-lg !default;
+$navbar-toggler-border-radius:      $btn-border-radius !default;
+
+$navbar-dark-color:                 rgba($white, .5) !default;
+$navbar-dark-hover-color:           rgba($white, .75) !default;
+$navbar-dark-active-color:          $white !default;
+$navbar-dark-disabled-color:        rgba($white, .25) !default;
+$navbar-dark-toggler-icon-bg:       str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='#{$navbar-dark-color}' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"), "#", "%23") !default;
+$navbar-dark-toggler-border-color:  rgba($white, .1) !default;
+
+$navbar-light-color:                rgba($black, .5) !default;
+$navbar-light-hover-color:          rgba($black, .7) !default;
+$navbar-light-active-color:         rgba($black, .9) !default;
+$navbar-light-disabled-color:       rgba($black, .3) !default;
+$navbar-light-toggler-icon-bg:      str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='#{$navbar-light-color}' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"), "#", "%23") !default;
+$navbar-light-toggler-border-color: rgba($black, .1) !default;
+
+// Pagination
+
+$pagination-padding-y:              .5rem !default;
+$pagination-padding-x:              .75rem !default;
+$pagination-padding-y-sm:           .25rem !default;
+$pagination-padding-x-sm:           .5rem !default;
+$pagination-padding-y-lg:           .75rem !default;
+$pagination-padding-x-lg:           1.5rem !default;
+$pagination-line-height:            1.25 !default;
+
+$pagination-color:                  $link-color !default;
+$pagination-bg:                     $white !default;
+$pagination-border-width:           $border-width !default;
+$pagination-border-color:           $gray-300 !default;
+
+$pagination-focus-box-shadow:       $input-btn-focus-box-shadow !default;
+
+$pagination-hover-color:            $link-hover-color !default;
+$pagination-hover-bg:               $gray-200 !default;
+$pagination-hover-border-color:     $gray-300 !default;
+
+$pagination-active-color:           $component-active-color !default;
+$pagination-active-bg:              $component-active-bg !default;
+$pagination-active-border-color:    $pagination-active-bg !default;
+
+$pagination-disabled-color:         $gray-600 !default;
+$pagination-disabled-bg:            $white !default;
+$pagination-disabled-border-color:  $gray-300 !default;
+
+
+// Jumbotron
+
+$jumbotron-padding:                 2rem !default;
+$jumbotron-bg:                      $gray-200 !default;
+
+
+// Cards
+
+$card-spacer-y:                     .75rem !default;
+$card-spacer-x:                     1.25rem !default;
+$card-border-width:                 $border-width !default;
+$card-border-radius:                $border-radius !default;
+$card-border-color:                 rgba($black, .125) !default;
+$card-inner-border-radius:          calc(#{$card-border-radius} - #{$card-border-width}) !default;
+$card-cap-bg:                       rgba($black, .03) !default;
+$card-bg:                           $white !default;
+
+$card-img-overlay-padding:          1.25rem !default;
+
+$card-group-margin:                 ($grid-gutter-width / 2) !default;
+$card-deck-margin:                  $card-group-margin !default;
+
+$card-columns-count:                3 !default;
+$card-columns-gap:                  1.25rem !default;
+$card-columns-margin:               $card-spacer-y !default;
+
+
+// Tooltips
+
+$tooltip-font-size:           $font-size-sm !default;
+$tooltip-max-width:           200px !default;
+$tooltip-color:               $white !default;
+$tooltip-bg:                  $black !default;
+$tooltip-border-radius:        $border-radius !default;
+$tooltip-opacity:             .9 !default;
+$tooltip-padding-y:           .25rem !default;
+$tooltip-padding-x:           .5rem !default;
+$tooltip-margin:              0 !default;
+
+$tooltip-arrow-width:         .8rem !default;
+$tooltip-arrow-height:        .4rem !default;
+$tooltip-arrow-color:         $tooltip-bg !default;
+
+
+// Popovers
+
+$popover-font-size:                 $font-size-sm !default;
+$popover-bg:                        $white !default;
+$popover-max-width:                 276px !default;
+$popover-border-width:              $border-width !default;
+$popover-border-color:              rgba($black, .2) !default;
+$popover-border-radius:             $border-radius-lg !default;
+$popover-box-shadow:                0 .25rem .5rem rgba($black, .2) !default;
+
+$popover-header-bg:                 darken($popover-bg, 3%) !default;
+$popover-header-color:              $headings-color !default;
+$popover-header-padding-y:          .5rem !default;
+$popover-header-padding-x:          .75rem !default;
+
+$popover-body-color:                $body-color !default;
+$popover-body-padding-y:            $popover-header-padding-y !default;
+$popover-body-padding-x:            $popover-header-padding-x !default;
+
+$popover-arrow-width:               1rem !default;
+$popover-arrow-height:              .5rem !default;
+$popover-arrow-color:               $popover-bg !default;
+
+$popover-arrow-outer-color:         fade-in($popover-border-color, .05) !default;
+
+
+// Badges
+
+$badge-font-size:                   75% !default;
+$badge-font-weight:                 $font-weight-bold !default;
+$badge-padding-y:                   .25em !default;
+$badge-padding-x:                   .4em !default;
+$badge-border-radius:               $border-radius !default;
+
+$badge-pill-padding-x:              .6em !default;
+// Use a higher than normal value to ensure completely rounded edges when
+// customizing padding or font-size on labels.
+$badge-pill-border-radius:          10rem !default;
+
+
+// Modals
+
+// Padding applied to the modal body
+$modal-inner-padding:         1rem !default;
+
+$modal-dialog-margin:         .5rem !default;
+$modal-dialog-margin-y-sm-up: 1.75rem !default;
+
+$modal-title-line-height:           $line-height-base !default;
+
+$modal-content-bg:               $white !default;
+$modal-content-border-color:     rgba($black, .2) !default;
+$modal-content-border-width:     $border-width !default;
+$modal-content-box-shadow-xs:    0 .25rem .5rem rgba($black, .5) !default;
+$modal-content-box-shadow-sm-up: 0 .5rem 1rem rgba($black, .5) !default;
+
+$modal-backdrop-bg:           $black !default;
+$modal-backdrop-opacity:      .5 !default;
+$modal-header-border-color:   $gray-200 !default;
+$modal-footer-border-color:   $modal-header-border-color !default;
+$modal-header-border-width:   $modal-content-border-width !default;
+$modal-footer-border-width:   $modal-header-border-width !default;
+$modal-header-padding:        1rem !default;
+
+$modal-lg:                          800px !default;
+$modal-md:                          500px !default;
+$modal-sm:                          300px !default;
+
+$modal-transition:                  transform .3s ease-out !default;
+
+
+// Alerts
+//
+// Define alert colors, border radius, and padding.
+
+$alert-padding-y:                   .75rem !default;
+$alert-padding-x:                   1.25rem !default;
+$alert-margin-bottom:               1rem !default;
+$alert-border-radius:               $border-radius !default;
+$alert-link-font-weight:            $font-weight-bold !default;
+$alert-border-width:                $border-width !default;
+
+$alert-bg-level:                    -10 !default;
+$alert-border-level:                -9 !default;
+$alert-color-level:                 6 !default;
+
+
+// Progress bars
+
+$progress-height:                   1rem !default;
+$progress-font-size:                ($font-size-base * .75) !default;
+$progress-bg:                       $gray-200 !default;
+$progress-border-radius:            $border-radius !default;
+$progress-box-shadow:               inset 0 .1rem .1rem rgba($black, .1) !default;
+$progress-bar-color:                $white !default;
+$progress-bar-bg:                   theme-color("primary") !default;
+$progress-bar-animation-timing:     1s linear infinite !default;
+$progress-bar-transition:           width .6s ease !default;
+
+// List group
+
+$list-group-bg:                     $white !default;
+$list-group-border-color:           rgba($black, .125) !default;
+$list-group-border-width:           $border-width !default;
+$list-group-border-radius:          $border-radius !default;
+
+$list-group-item-padding-y:         .75rem !default;
+$list-group-item-padding-x:         1.25rem !default;
+
+$list-group-hover-bg:               $gray-100 !default;
+$list-group-active-color:           $component-active-color !default;
+$list-group-active-bg:              $component-active-bg !default;
+$list-group-active-border-color:    $list-group-active-bg !default;
+
+$list-group-disabled-color:         $gray-600 !default;
+$list-group-disabled-bg:            $list-group-bg !default;
+
+$list-group-action-color:           $gray-700 !default;
+$list-group-action-hover-color:     $list-group-action-color !default;
+
+$list-group-action-active-color:    $body-color !default;
+$list-group-action-active-bg:       $gray-200 !default;
+
+
+// Image thumbnails
+
+$thumbnail-padding:                 .25rem !default;
+$thumbnail-bg:                      $body-bg !default;
+$thumbnail-border-width:            $border-width !default;
+$thumbnail-border-color:            $gray-300 !default;
+$thumbnail-border-radius:           $border-radius !default;
+$thumbnail-box-shadow:              0 1px 2px rgba($black, .075) !default;
+
+
+// Figures
+
+$figure-caption-font-size:          90% !default;
+$figure-caption-color:              $gray-600 !default;
+
+
+// Breadcrumbs
+
+$breadcrumb-padding-y:              .75rem !default;
+$breadcrumb-padding-x:              1rem !default;
+$breadcrumb-item-padding:           .5rem !default;
+
+$breadcrumb-margin-bottom:          1rem !default;
+
+$breadcrumb-bg:                     $gray-200 !default;
+$breadcrumb-divider-color:          $gray-600 !default;
+$breadcrumb-active-color:           $gray-600 !default;
+$breadcrumb-divider:                "/" !default;
+
+
+// Carousel
+
+$carousel-control-color:            $white !default;
+$carousel-control-width:            15% !default;
+$carousel-control-opacity:          .5 !default;
+
+$carousel-indicator-width:          30px !default;
+$carousel-indicator-height:         3px !default;
+$carousel-indicator-spacer:         3px !default;
+$carousel-indicator-active-bg:      $white !default;
+
+$carousel-caption-width:            70% !default;
+$carousel-caption-color:            $white !default;
+
+$carousel-control-icon-width:       20px !default;
+
+$carousel-control-prev-icon-bg:     str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='#{$carousel-control-color}' viewBox='0 0 8 8'%3E%3Cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3E%3C/svg%3E"), "#", "%23") !default;
+$carousel-control-next-icon-bg:     str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='#{$carousel-control-color}' viewBox='0 0 8 8'%3E%3Cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3E%3C/svg%3E"), "#", "%23") !default;
+
+$carousel-transition:               transform .6s ease !default;
+
+
+// Close
+
+$close-font-size:                   $font-size-base * 1.5 !default;
+$close-font-weight:                 $font-weight-bold !default;
+$close-color:                       $black !default;
+$close-text-shadow:                 0 1px 0 $white !default;
+
+// Code
+
+$code-font-size:                    87.5% !default;
+$code-color:                        $pink !default;
+
+$kbd-padding-y:                     .2rem !default;
+$kbd-padding-x:                     .4rem !default;
+$kbd-font-size:                     $code-font-size !default;
+$kbd-color:                         $white !default;
+$kbd-bg:                            $gray-900 !default;
+
+$pre-color:                         $gray-900 !default;
+$pre-scrollable-max-height:         340px !default;
+
+
+// Printing
+$print-page-size:                   a3 !default;
+$print-body-min-width:              map-get($grid-breakpoints, "lg") !default;
diff --git a/webapp/src/scss/bootstrap-grid.scss b/webapp/src/scss/bootstrap-grid.scss
new file mode 100644
index 0000000000000000000000000000000000000000..26c0dc89c284609ece9e6f9f14a53ba3d708107e
--- /dev/null
+++ b/webapp/src/scss/bootstrap-grid.scss
@@ -0,0 +1,32 @@
+/*!
+ * Bootstrap Grid v4.0.0 (https://getbootstrap.com)
+ * Copyright 2011-2018 The Bootstrap Authors
+ * Copyright 2011-2018 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ */
+
+@at-root {
+  @-ms-viewport { width: device-width; } // stylelint-disable-line at-rule-no-vendor-prefix
+}
+
+html {
+  box-sizing: border-box;
+  -ms-overflow-style: scrollbar;
+}
+
+*,
+*::before,
+*::after {
+  box-sizing: inherit;
+}
+
+@import "functions";
+@import "variables";
+
+@import "mixins/breakpoints";
+@import "mixins/grid-framework";
+@import "mixins/grid";
+
+@import "grid";
+@import "utilities/display";
+@import "utilities/flex";
diff --git a/webapp/src/scss/mixins/_breakpoints.scss b/webapp/src/scss/mixins/_breakpoints.scss
new file mode 100644
index 0000000000000000000000000000000000000000..d1ad684cc76e58d07333a38e0d6e0451b8f47de8
--- /dev/null
+++ b/webapp/src/scss/mixins/_breakpoints.scss
@@ -0,0 +1,123 @@
+// Breakpoint viewport sizes and media queries.
+//
+// Breakpoints are defined as a map of (name: minimum width), order from small to large:
+//
+//    (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px)
+//
+// The map defined in the `$grid-breakpoints` global variable is used as the `$breakpoints` argument by default.
+
+// Name of the next breakpoint, or null for the last breakpoint.
+//
+//    >> breakpoint-next(sm)
+//    md
+//    >> breakpoint-next(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))
+//    md
+//    >> breakpoint-next(sm, $breakpoint-names: (xs sm md lg xl))
+//    md
+@function breakpoint-next($name, $breakpoints: $grid-breakpoints, $breakpoint-names: map-keys($breakpoints)) {
+  $n: index($breakpoint-names, $name);
+  @return if($n < length($breakpoint-names), nth($breakpoint-names, $n + 1), null);
+}
+
+// Minimum breakpoint width. Null for the smallest (first) breakpoint.
+//
+//    >> breakpoint-min(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))
+//    576px
+@function breakpoint-min($name, $breakpoints: $grid-breakpoints) {
+  $min: map-get($breakpoints, $name);
+  @return if($min != 0, $min, null);
+}
+
+// Maximum breakpoint width. Null for the largest (last) breakpoint.
+// The maximum value is calculated as the minimum of the next one less 0.02px
+// to work around the limitations of `min-` and `max-` prefixes and viewports with fractional widths.
+// See https://www.w3.org/TR/mediaqueries-4/#mq-min-max
+// Uses 0.02px rather than 0.01px to work around a current rounding bug in Safari.
+// See https://bugs.webkit.org/show_bug.cgi?id=178261
+//
+//    >> breakpoint-max(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))
+//    767.98px
+@function breakpoint-max($name, $breakpoints: $grid-breakpoints) {
+  $next: breakpoint-next($name, $breakpoints);
+  @return if($next, breakpoint-min($next, $breakpoints) - .02px, null);
+}
+
+// Returns a blank string if smallest breakpoint, otherwise returns the name with a dash infront.
+// Useful for making responsive utilities.
+//
+//    >> breakpoint-infix(xs, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))
+//    ""  (Returns a blank string)
+//    >> breakpoint-infix(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))
+//    "-sm"
+@function breakpoint-infix($name, $breakpoints: $grid-breakpoints) {
+  @return if(breakpoint-min($name, $breakpoints) == null, "", "-#{$name}");
+}
+
+// Media of at least the minimum breakpoint width. No query for the smallest breakpoint.
+// Makes the @content apply to the given breakpoint and wider.
+@mixin media-breakpoint-up($name, $breakpoints: $grid-breakpoints) {
+  $min: breakpoint-min($name, $breakpoints);
+  @if $min {
+    @media (min-width: $min) {
+      @content;
+    }
+  } @else {
+    @content;
+  }
+}
+
+// Media of at most the maximum breakpoint width. No query for the largest breakpoint.
+// Makes the @content apply to the given breakpoint and narrower.
+@mixin media-breakpoint-down($name, $breakpoints: $grid-breakpoints) {
+  $max: breakpoint-max($name, $breakpoints);
+  @if $max {
+    @media (max-width: $max) {
+      @content;
+    }
+  } @else {
+    @content;
+  }
+}
+
+// Media that spans multiple breakpoint widths.
+// Makes the @content apply between the min and max breakpoints
+@mixin media-breakpoint-between($lower, $upper, $breakpoints: $grid-breakpoints) {
+  $min: breakpoint-min($lower, $breakpoints);
+  $max: breakpoint-max($upper, $breakpoints);
+
+  @if $min != null and $max != null {
+    @media (min-width: $min) and (max-width: $max) {
+      @content;
+    }
+  } @else if $max == null {
+    @include media-breakpoint-up($lower, $breakpoints) {
+      @content;
+    }
+  } @else if $min == null {
+    @include media-breakpoint-down($upper, $breakpoints) {
+      @content;
+    }
+  }
+}
+
+// Media between the breakpoint's minimum and maximum widths.
+// No minimum for the smallest breakpoint, and no maximum for the largest one.
+// Makes the @content apply only to the given breakpoint, not viewports any wider or narrower.
+@mixin media-breakpoint-only($name, $breakpoints: $grid-breakpoints) {
+  $min: breakpoint-min($name, $breakpoints);
+  $max: breakpoint-max($name, $breakpoints);
+
+  @if $min != null and $max != null {
+    @media (min-width: $min) and (max-width: $max) {
+      @content;
+    }
+  } @else if $max == null {
+    @include media-breakpoint-up($name, $breakpoints) {
+      @content;
+    }
+  } @else if $min == null {
+    @include media-breakpoint-down($name, $breakpoints) {
+      @content;
+    }
+  }
+}
diff --git a/webapp/src/scss/mixins/_grid-framework.scss b/webapp/src/scss/mixins/_grid-framework.scss
new file mode 100644
index 0000000000000000000000000000000000000000..7b37f868f198be0b678dda85a84bc058c247efeb
--- /dev/null
+++ b/webapp/src/scss/mixins/_grid-framework.scss
@@ -0,0 +1,67 @@
+// Framework grid generation
+//
+// Used only by Bootstrap to generate the correct number of grid classes given
+// any value of `$grid-columns`.
+
+@mixin make-grid-columns($columns: $grid-columns, $gutter: $grid-gutter-width, $breakpoints: $grid-breakpoints) {
+  // Common properties for all breakpoints
+  %grid-column {
+    position: relative;
+    width: 100%;
+    min-height: 1px; // Prevent columns from collapsing when empty
+    padding-right: ($gutter / 2);
+    padding-left: ($gutter / 2);
+  }
+
+  @each $breakpoint in map-keys($breakpoints) {
+    $infix: breakpoint-infix($breakpoint, $breakpoints);
+
+    // Allow columns to stretch full width below their breakpoints
+    @for $i from 1 through $columns {
+      .col#{$infix}-#{$i} {
+        @extend %grid-column;
+      }
+    }
+    .col#{$infix},
+    .col#{$infix}-auto {
+      @extend %grid-column;
+    }
+
+    @include media-breakpoint-up($breakpoint, $breakpoints) {
+      // Provide basic `.col-{bp}` classes for equal-width flexbox columns
+      .col#{$infix} {
+        flex-basis: 0;
+        flex-grow: 1;
+        max-width: 100%;
+      }
+      .col#{$infix}-auto {
+        flex: 0 0 auto;
+        width: auto;
+        max-width: none; // Reset earlier grid tiers
+      }
+
+      @for $i from 1 through $columns {
+        .col#{$infix}-#{$i} {
+          @include make-col($i, $columns);
+        }
+      }
+
+      .order#{$infix}-first { order: -1; }
+
+      .order#{$infix}-last { order: $columns + 1; }
+
+      @for $i from 0 through $columns {
+        .order#{$infix}-#{$i} { order: $i; }
+      }
+
+      // `$columns - 1` because offsetting by the width of an entire row isn't possible
+      @for $i from 0 through ($columns - 1) {
+        @if not ($infix == "" and $i == 0) { // Avoid emitting useless .offset-0
+          .offset#{$infix}-#{$i} {
+            @include make-col-offset($i, $columns);
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/webapp/src/scss/mixins/_grid.scss b/webapp/src/scss/mixins/_grid.scss
new file mode 100644
index 0000000000000000000000000000000000000000..b75ebcbca0ceec5d315ec319b7cd1a5907c49f19
--- /dev/null
+++ b/webapp/src/scss/mixins/_grid.scss
@@ -0,0 +1,52 @@
+/// Grid system
+//
+// Generate semantic grid columns with these mixins.
+
+@mixin make-container() {
+  width: 100%;
+  padding-right: ($grid-gutter-width / 2);
+  padding-left: ($grid-gutter-width / 2);
+  margin-right: auto;
+  margin-left: auto;
+}
+
+
+// For each breakpoint, define the maximum width of the container in a media query
+@mixin make-container-max-widths($max-widths: $container-max-widths, $breakpoints: $grid-breakpoints) {
+  @each $breakpoint, $container-max-width in $max-widths {
+    @include media-breakpoint-up($breakpoint, $breakpoints) {
+      max-width: $container-max-width;
+    }
+  }
+}
+
+@mixin make-row() {
+  display: flex;
+  flex-wrap: wrap;
+  margin-right: ($grid-gutter-width / -2);
+  margin-left: ($grid-gutter-width / -2);
+}
+
+@mixin make-col-ready() {
+  position: relative;
+  // Prevent columns from becoming too narrow when at smaller grid tiers by
+  // always setting `width: 100%;`. This works because we use `flex` values
+  // later on to override this initial width.
+  width: 100%;
+  min-height: 1px; // Prevent collapsing
+  padding-right: ($grid-gutter-width / 2);
+  padding-left: ($grid-gutter-width / 2);
+}
+
+@mixin make-col($size, $columns: $grid-columns) {
+  flex: 0 0 percentage($size / $columns);
+  // Add a `max-width` to ensure content within each column does not blow out
+  // the width of the column. Applies to IE10+ and Firefox. Chrome and Safari
+  // do not appear to require this.
+  max-width: percentage($size / $columns);
+}
+
+@mixin make-col-offset($size, $columns: $grid-columns) {
+  $num: $size / $columns;
+  margin-left: if($num == 0, 0, percentage($num));
+}
diff --git a/webapp/src/scss/utilities/_display.scss b/webapp/src/scss/utilities/_display.scss
new file mode 100644
index 0000000000000000000000000000000000000000..20aeeb5f3e7586bba0fe69bff02057b15b2309f3
--- /dev/null
+++ b/webapp/src/scss/utilities/_display.scss
@@ -0,0 +1,38 @@
+// stylelint-disable declaration-no-important
+
+//
+// Utilities for common `display` values
+//
+
+@each $breakpoint in map-keys($grid-breakpoints) {
+  @include media-breakpoint-up($breakpoint) {
+    $infix: breakpoint-infix($breakpoint, $grid-breakpoints);
+
+    .d#{$infix}-none         { display: none !important; }
+    .d#{$infix}-inline       { display: inline !important; }
+    .d#{$infix}-inline-block { display: inline-block !important; }
+    .d#{$infix}-block        { display: block !important; }
+    .d#{$infix}-table        { display: table !important; }
+    .d#{$infix}-table-row    { display: table-row !important; }
+    .d#{$infix}-table-cell   { display: table-cell !important; }
+    .d#{$infix}-flex         { display: flex !important; }
+    .d#{$infix}-inline-flex  { display: inline-flex !important; }
+  }
+}
+
+
+//
+// Utilities for toggling `display` in print
+//
+
+@media print {
+  .d-print-none         { display: none !important; }
+  .d-print-inline       { display: inline !important; }
+  .d-print-inline-block { display: inline-block !important; }
+  .d-print-block        { display: block !important; }
+  .d-print-table        { display: table !important; }
+  .d-print-table-row    { display: table-row !important; }
+  .d-print-table-cell   { display: table-cell !important; }
+  .d-print-flex         { display: flex !important; }
+  .d-print-inline-flex  { display: inline-flex !important; }
+}
diff --git a/webapp/src/scss/utilities/_flex.scss b/webapp/src/scss/utilities/_flex.scss
new file mode 100644
index 0000000000000000000000000000000000000000..8e470384362f4d46c1b27e743f03e4eaed80324f
--- /dev/null
+++ b/webapp/src/scss/utilities/_flex.scss
@@ -0,0 +1,46 @@
+// stylelint-disable declaration-no-important
+
+// Flex variation
+//
+// Custom styles for additional flex alignment options.
+
+@each $breakpoint in map-keys($grid-breakpoints) {
+  @include media-breakpoint-up($breakpoint) {
+    $infix: breakpoint-infix($breakpoint, $grid-breakpoints);
+
+    .flex#{$infix}-row            { flex-direction: row !important; }
+    .flex#{$infix}-column         { flex-direction: column !important; }
+    .flex#{$infix}-row-reverse    { flex-direction: row-reverse !important; }
+    .flex#{$infix}-column-reverse { flex-direction: column-reverse !important; }
+
+    .flex#{$infix}-wrap         { flex-wrap: wrap !important; }
+    .flex#{$infix}-nowrap       { flex-wrap: nowrap !important; }
+    .flex#{$infix}-wrap-reverse { flex-wrap: wrap-reverse !important; }
+
+    .justify-content#{$infix}-start   { justify-content: flex-start !important; }
+    .justify-content#{$infix}-end     { justify-content: flex-end !important; }
+    .justify-content#{$infix}-center  { justify-content: center !important; }
+    .justify-content#{$infix}-between { justify-content: space-between !important; }
+    .justify-content#{$infix}-around  { justify-content: space-around !important; }
+
+    .align-items#{$infix}-start    { align-items: flex-start !important; }
+    .align-items#{$infix}-end      { align-items: flex-end !important; }
+    .align-items#{$infix}-center   { align-items: center !important; }
+    .align-items#{$infix}-baseline { align-items: baseline !important; }
+    .align-items#{$infix}-stretch  { align-items: stretch !important; }
+
+    .align-content#{$infix}-start   { align-content: flex-start !important; }
+    .align-content#{$infix}-end     { align-content: flex-end !important; }
+    .align-content#{$infix}-center  { align-content: center !important; }
+    .align-content#{$infix}-between { align-content: space-between !important; }
+    .align-content#{$infix}-around  { align-content: space-around !important; }
+    .align-content#{$infix}-stretch { align-content: stretch !important; }
+
+    .align-self#{$infix}-auto     { align-self: auto !important; }
+    .align-self#{$infix}-start    { align-self: flex-start !important; }
+    .align-self#{$infix}-end      { align-self: flex-end !important; }
+    .align-self#{$infix}-center   { align-self: center !important; }
+    .align-self#{$infix}-baseline { align-self: baseline !important; }
+    .align-self#{$infix}-stretch  { align-self: stretch !important; }
+  }
+}
diff --git a/webapp/src/styles.scss b/webapp/src/styles.scss
index 90d4ee0072ce3fc41812f8af910219f9eea3c3de..df64f3c452e1de8a5b7dad7bfe21e7db81cc51c6 100644
--- a/webapp/src/styles.scss
+++ b/webapp/src/styles.scss
@@ -1 +1,19 @@
 /* You can add global styles to this file, and also import other style files */
+
+@import '~scss/bootstrap-grid.scss';
+
+html,
+body {
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+  margin: 0;
+  padding: 0;
+  height: 100%;
+}
+
+h1, h2, h3 {
+  font-size: 2em;
+  font-weight: 700;
+  padding-left: 0.5em;
+  margin: 0;
+  flex: 1;
+}
\ No newline at end of file
diff --git a/webapp/src/theme.scss b/webapp/src/theme.scss
new file mode 100644
index 0000000000000000000000000000000000000000..9feaf1a174397fd34508d3b221d7d46e1239cba6
--- /dev/null
+++ b/webapp/src/theme.scss
@@ -0,0 +1,17 @@
+// Charger les outils Material Design
+@import '~@angular/material/theming';
+
+// Charger les mixins de theming des composants
+@import 'theming/all-theme';
+
+// Charger le thème principal
+@import 'theming/grandlyon/grandlyon';
+
+// Le mixin `mat-core()` ne doit être appélé qu'une seule fois puisqu'il génère
+// des styles CSS.
+// TODO: passer en argument la configuration typographique.
+@include mat-core();
+
+@include angular-material-theme($gl-theme);
+@include app-theme($gl-theme);
+
diff --git a/webapp/src/theming/_all-theme.scss b/webapp/src/theming/_all-theme.scss
new file mode 100644
index 0000000000000000000000000000000000000000..50310d0330dc28b2fbba8590f60ac6e6e5396e86
--- /dev/null
+++ b/webapp/src/theming/_all-theme.scss
@@ -0,0 +1,29 @@
+@mixin app-theme($theme) {
+  // @include app-shared-theme($theme);
+  // @include app-components-theme($theme);
+}
+
+// Theming des éléments transverses
+
+// @import "shared/form-theme";
+// @import "shared/loader-theme";
+// @import "shared/table-theme";
+
+// @mixin app-shared-theme($theme) {
+//   @include app-shared-form-theme($theme);
+//   @include app-shared-loader-theme($theme);
+//   @include app-shared-table-theme($theme);
+// }
+
+// // Theming des composants Angular
+
+// @import "~app/app-theme.component";
+// @import "~app/components/menu/menu-theme.component";
+// @import "~app/components/federal/welcome/welcome-theme.component";
+
+// @mixin app-components-theme($theme) {
+//   @include app-app-theme($theme);
+//   @include app-menu-theme($theme);
+//   @include app-welcome-theme($theme);
+// }
+
diff --git a/webapp/src/theming/_theming.scss b/webapp/src/theming/_theming.scss
new file mode 100644
index 0000000000000000000000000000000000000000..b09f09c57fa6a1b565890b3d74517a6d3bde9097
--- /dev/null
+++ b/webapp/src/theming/_theming.scss
@@ -0,0 +1,11 @@
+@import "~sass-recursive-map-merge/recursive-map-merge";
+
+@function app-override-theme($mat-theme, $foreground: null, $background: null) {
+  $theme: (
+    foreground: $foreground,
+    background: $background,
+  );
+
+  @return recursive-map-merge($mat-theme, $theme);
+}
+
diff --git a/webapp/src/theming/grandlyon/config/_palette.scss b/webapp/src/theming/grandlyon/config/_palette.scss
new file mode 100644
index 0000000000000000000000000000000000000000..eb2416e0684178388d861e60dbcacc20f322bdcc
--- /dev/null
+++ b/webapp/src/theming/grandlyon/config/_palette.scss
@@ -0,0 +1,89 @@
+// Color palettes for Grand Lyon, la Métropole
+//
+// Generator:
+// http://mcg.mbitson.com/#!?vermilion=%23e10000&woodsmoke=%23050707&themename=grandlyon
+
+$gl-dark-primary-text: black;
+$gl-light-primary-text: white;
+
+$gl-vermilion: (
+    50:   #ffebeb,
+    100:  #ff9f9f,
+    200:  #ff6767,
+    300:  #ff1f1f,
+    400:  #ff0101,
+    500:  #e10000,
+    600:  #c20000,
+    700:  #a40000,
+    800:  #850000,
+    900:  #670000,
+    A100: #ffe1e1,
+    A200: #ff7b7b,
+    A400: #ff1515,
+    A700: #fa0000,
+    contrast: (
+        50:   $gl-dark-primary-text,
+        100:  $gl-dark-primary-text,
+        200:  $gl-dark-primary-text,
+        300:  $gl-light-primary-text,
+        400:  $gl-light-primary-text,
+        500:  $gl-light-primary-text,
+        600:  $gl-light-primary-text,
+        700:  $gl-light-primary-text,
+        800:  $gl-light-primary-text,
+        900:  $gl-light-primary-text,
+        A100: $gl-dark-primary-text,
+        A200: $gl-dark-primary-text,
+        A400: $gl-light-primary-text,
+        A700: $gl-light-primary-text,
+    )
+);
+
+$gl-woodsmoke: (
+    50:   #e1e1e1,
+    100:  #b4b5b5,
+    200:  #828383,
+    300:  #505151,
+    400:  #2b2c2c,
+    500:  #050707,
+    600:  #040606,
+    700:  #040505,
+    800:  #030404,
+    900:  #010202,
+    A100: #a6a6a6,
+    A200: #8c8c8c,
+    A400: #737373,
+    A700: #666666,
+    contrast: (
+        50:   $gl-dark-primary-text,
+        100:  $gl-dark-primary-text,
+        200:  $gl-dark-primary-text,
+        300:  $gl-light-primary-text,
+        400:  $gl-light-primary-text,
+        500:  $gl-light-primary-text,
+        600:  $gl-light-primary-text,
+        700:  $gl-light-primary-text,
+        800:  $gl-light-primary-text,
+        900:  $gl-light-primary-text,
+        A100: $gl-dark-primary-text,
+        A200: $gl-dark-primary-text,
+        A400: $gl-light-primary-text,
+        A700: $gl-light-primary-text,
+    )
+);
+
+// Palette des couleurs d'arrière plan spécifique au thème clair.
+$gl-light-theme-background: (
+  // spécifiques
+  actived-navigation: #f0f0f0,
+  form: rgba(220, 220, 220, 0.4),
+  table-header: #d3d3d3,
+);
+
+// Palette des couleurs de premier plan spécifique au thème clair.
+$gl-light-theme-foreground: (
+  // spécifiques
+  valid:   #4caf50,
+  invalid: #d32f2f,
+);
+
diff --git a/webapp/src/theming/grandlyon/config/_variables.scss b/webapp/src/theming/grandlyon/config/_variables.scss
new file mode 100644
index 0000000000000000000000000000000000000000..2ac14af97461419e2f758924aeddaf66f16d1341
--- /dev/null
+++ b/webapp/src/theming/grandlyon/config/_variables.scss
@@ -0,0 +1,9 @@
+@import "palette";
+
+// Palettes
+// --------
+
+$gl-theme-primary: mat-palette($gl-vermilion);
+$gl-theme-accent:  mat-palette($gl-woodsmoke);
+$gl-theme-warn:    mat-palette($gl-woodsmoke);
+
diff --git a/webapp/src/theming/grandlyon/grandlyon.scss b/webapp/src/theming/grandlyon/grandlyon.scss
new file mode 100644
index 0000000000000000000000000000000000000000..73837988d9dfb56153cfb79fe56e02f8e0ca8f0b
--- /dev/null
+++ b/webapp/src/theming/grandlyon/grandlyon.scss
@@ -0,0 +1,10 @@
+@import '../theming';
+
+@import "config/variables";
+
+$gl-theme: app-override-theme(
+  mat-light-theme($gl-theme-primary, $gl-theme-accent, $gl-theme-warn),
+  $foreground: $gl-light-theme-foreground,
+  $background: $gl-light-theme-background
+);
+