diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 3b3be1facc2623141387e8ff11fefbe110045f56..828c72a42b9210f37f3964923909fbf0649081ff 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -2,6 +2,8 @@ variables:
   BASE_HREF: nshmp
   GITLAB_TOKEN: '${CI_JOB_TOKEN}'
   IMAGE_NAME: ${CODE_REGISTRY_IMAGE}/${CI_PROJECT_NAME}:${ENVIRONMENT}-${CI_COMMIT_SHORT_SHA}
+  npm_config_cache: '$CI_PROJECT_DIR/.npm'
+  CYPRESS_CACHE_FOLDER: '$CI_PROJECT_DIR/cache/Cypress'
 
 # Do not run for merge requests
 workflow:
@@ -136,6 +138,8 @@ stages:
 Init:
   artifacts:
     paths:
+      - .npm
+      - cache/Cypress
       - node_modules
   image: ${DEVOPS_REGISTRY}usgs/node:16
   script:
@@ -163,6 +167,10 @@ NPM Audit:
 ####
 
 Build Project:
+  artifacts:
+    paths:
+      - .angular
+      - dist
   image: ${DEVOPS_REGISTRY}usgs/node:16
   needs:
     - Init
@@ -210,10 +218,20 @@ Lint:
 
 End to End Tests:
   image: cypress/base:16.14.0
-  needs: []
+  needs:
+    - Init
+    - Build Project
+  parallel:
+    matrix:
+      - CMD: cy:run:dashboard
+      - CMD: cy:run:dev
+      - CMD: cy:run:gmm
+      - CMD: cy:run:hazard
+      - CMD: cy:run:services
+      - CMD: cy:run:source-models
   script:
-    - npm ci
-    - npm run cy:run
+    - npm rebuild
+    - npm run ${CMD}
   stage: test
   tags:
     - build
diff --git a/angular.json b/angular.json
index fd4bf1668c9aa6daf4187c5cb2fd3634e8c70115..86e831e8bdf537606f8776fe105526ab6d6f8119 100644
--- a/angular.json
+++ b/angular.json
@@ -181,7 +181,7 @@
         "e2e": {
           "builder": "@nrwl/cypress:cypress",
           "options": {
-            "cypressConfig": "projects/nshmp-apps/cypress.json",
+            "cypressConfig": "projects/nshmp-apps/cypress/config/cypress-all.json",
             "tsConfig": "projects/nshmp-apps/cypress/tsconfig.json",
             "devServerTarget": "nshmp-apps:serve"
           },
@@ -405,6 +405,9 @@
   "defaultProject": "nshmp-apps",
   "cli": {
     "analytics": false,
-    "defaultCollection": "@ngrx/schematics"
+    "defaultCollection": "@ngrx/schematics",
+    "cache": {
+      "environment": "all"
+    }
   }
 }
diff --git a/package-lock.json b/package-lock.json
index 763082b775bf7be24bcc021b58261a614007f036..6cebd28d31e4a905b84ff65167d4885ab1d9ecbd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -19,8 +19,8 @@
         "@angular/platform-browser-dynamic": "^13.2.6",
         "@angular/router": "^13.2.6",
         "@ghsc/disagg-d3": "^0.6.0",
-        "@ghsc/nshmp-template": "^13.0.1",
-        "@ghsc/nshmp-utils": "^4.4.0",
+        "@ghsc/nshmp-template": "^13.0.2",
+        "@ghsc/nshmp-utils": "^4.4.1",
         "@mapbox/mapbox-gl-geocoder": "^5.0.0",
         "@ngrx/effects": "^13.0.2",
         "@ngrx/router-store": "^13.0.2",
@@ -95,6 +95,7 @@
         "protractor": "~7.0.0",
         "ts-loader": "^9.2.8",
         "ts-node": "^8.10.2",
+        "tsconfig-paths": "^3.14.1",
         "tsutils": "^3.21.0",
         "typescript": "~4.5.5"
       }
@@ -3821,9 +3822,9 @@
       }
     },
     "node_modules/@ghsc/nshmp-template": {
-      "version": "13.0.1",
-      "resolved": "https://code.usgs.gov/api/v4/projects/1416/packages/npm/@ghsc/nshmp-template/-/@ghsc/nshmp-template-13.0.1.tgz",
-      "integrity": "sha1-j3g0VMEgC5Uxzr09QgtM5NMVXMM=",
+      "version": "13.0.2",
+      "resolved": "https://code.usgs.gov/api/v4/projects/1416/packages/npm/@ghsc/nshmp-template/-/@ghsc/nshmp-template-13.0.2.tgz",
+      "integrity": "sha1-LGJgCC4bEs2UlEKAFY6G+dQph+I=",
       "dependencies": {
         "tslib": "^2.0.0"
       },
@@ -3833,9 +3834,9 @@
       }
     },
     "node_modules/@ghsc/nshmp-utils": {
-      "version": "4.4.0",
-      "resolved": "https://code.usgs.gov/api/v4/projects/1414/packages/npm/@ghsc/nshmp-utils/-/@ghsc/nshmp-utils-4.4.0.tgz",
-      "integrity": "sha1-l5Eas0KsOvGc+Mdc3tAqELXEelY=",
+      "version": "4.4.1",
+      "resolved": "https://code.usgs.gov/api/v4/projects/1414/packages/npm/@ghsc/nshmp-utils/-/@ghsc/nshmp-utils-4.4.1.tgz",
+      "integrity": "sha1-RuJQwEDMEvLyyWE7NdNe/tliCoA=",
       "dependencies": {
         "@mapbox/geojson-extent": "^1.0.0",
         "change-case": "^4.1.2",
@@ -19928,9 +19929,9 @@
       }
     },
     "node_modules/minimist": {
-      "version": "1.2.5",
-      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
-      "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
+      "version": "1.2.6",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
+      "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
     },
     "node_modules/minimist-options": {
       "version": "4.1.0",
@@ -26325,14 +26326,14 @@
       }
     },
     "node_modules/tsconfig-paths": {
-      "version": "3.14.0",
-      "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.0.tgz",
-      "integrity": "sha512-cg/1jAZoL57R39+wiw4u/SCC6Ic9Q5NqjBOb+9xISedOYurfog9ZNmKJSxAnb2m/5Bq4lE9lhUcau33Ml8DM0g==",
+      "version": "3.14.1",
+      "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz",
+      "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==",
       "dev": true,
       "dependencies": {
         "@types/json5": "^0.0.29",
         "json5": "^1.0.1",
-        "minimist": "^1.2.0",
+        "minimist": "^1.2.6",
         "strip-bom": "^3.0.0"
       }
     },
@@ -30589,17 +30590,17 @@
       }
     },
     "@ghsc/nshmp-template": {
-      "version": "13.0.1",
-      "resolved": "https://code.usgs.gov/api/v4/projects/1416/packages/npm/@ghsc/nshmp-template/-/@ghsc/nshmp-template-13.0.1.tgz",
-      "integrity": "sha1-j3g0VMEgC5Uxzr09QgtM5NMVXMM=",
+      "version": "13.0.2",
+      "resolved": "https://code.usgs.gov/api/v4/projects/1416/packages/npm/@ghsc/nshmp-template/-/@ghsc/nshmp-template-13.0.2.tgz",
+      "integrity": "sha1-LGJgCC4bEs2UlEKAFY6G+dQph+I=",
       "requires": {
         "tslib": "^2.0.0"
       }
     },
     "@ghsc/nshmp-utils": {
-      "version": "4.4.0",
-      "resolved": "https://code.usgs.gov/api/v4/projects/1414/packages/npm/@ghsc/nshmp-utils/-/@ghsc/nshmp-utils-4.4.0.tgz",
-      "integrity": "sha1-l5Eas0KsOvGc+Mdc3tAqELXEelY=",
+      "version": "4.4.1",
+      "resolved": "https://code.usgs.gov/api/v4/projects/1414/packages/npm/@ghsc/nshmp-utils/-/@ghsc/nshmp-utils-4.4.1.tgz",
+      "integrity": "sha1-RuJQwEDMEvLyyWE7NdNe/tliCoA=",
       "requires": {
         "@mapbox/geojson-extent": "^1.0.0",
         "change-case": "^4.1.2",
@@ -42894,9 +42895,9 @@
       }
     },
     "minimist": {
-      "version": "1.2.5",
-      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
-      "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
+      "version": "1.2.6",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
+      "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
     },
     "minimist-options": {
       "version": "4.1.0",
@@ -47801,14 +47802,14 @@
       }
     },
     "tsconfig-paths": {
-      "version": "3.14.0",
-      "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.0.tgz",
-      "integrity": "sha512-cg/1jAZoL57R39+wiw4u/SCC6Ic9Q5NqjBOb+9xISedOYurfog9ZNmKJSxAnb2m/5Bq4lE9lhUcau33Ml8DM0g==",
+      "version": "3.14.1",
+      "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz",
+      "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==",
       "dev": true,
       "requires": {
         "@types/json5": "^0.0.29",
         "json5": "^1.0.1",
-        "minimist": "^1.2.0",
+        "minimist": "^1.2.6",
         "strip-bom": "^3.0.0"
       },
       "dependencies": {
diff --git a/package.json b/package.json
index bd54503e3b29a557d880ccb1703b54428b5bbc0e..3a48b8be22f2c954ef3c2713a683a03cc2626cf6 100644
--- a/package.json
+++ b/package.json
@@ -5,6 +5,12 @@
     "build": "ng build nshmp-apps",
     "build:prod": "ng build nshmp-apps --configuration production",
     "cy:run": "ng e2e nshmp-apps",
+    "cy:run:dashboard": "npm run cy:run -- --cypress-config=projects/nshmp-apps/cypress/config/cypress-dashboard.json",
+    "cy:run:dev": "npm run cy:run -- --cypress-config=projects/nshmp-apps/cypress/config/cypress-dev.json",
+    "cy:run:gmm": "npm run cy:run -- --cypress-config=projects/nshmp-apps/cypress/config/cypress-gmm.json",
+    "cy:run:hazard": "npm run cy:run -- --cypress-config=projects/nshmp-apps/cypress/config/cypress-hazard.json",
+    "cy:run:services": "npm run cy:run -- --cypress-config=projects/nshmp-apps/cypress/config/cypress-services.json",
+    "cy:run:source-models": "npm run cy:run -- --cypress-config=projects/nshmp-apps/cypress/config/cypress-source-models.json",
     "cy:verify": "cypress verify",
     "e2e": "ng e2e",
     "lint": "ng lint",
@@ -29,8 +35,8 @@
     "@angular/platform-browser-dynamic": "^13.2.6",
     "@angular/router": "^13.2.6",
     "@ghsc/disagg-d3": "^0.6.0",
-    "@ghsc/nshmp-template": "^13.0.1",
-    "@ghsc/nshmp-utils": "^4.4.0",
+    "@ghsc/nshmp-template": "^13.0.2",
+    "@ghsc/nshmp-utils": "^4.4.1",
     "@mapbox/mapbox-gl-geocoder": "^5.0.0",
     "@ngrx/effects": "^13.0.2",
     "@ngrx/router-store": "^13.0.2",
@@ -105,6 +111,7 @@
     "protractor": "~7.0.0",
     "ts-loader": "^9.2.8",
     "ts-node": "^8.10.2",
+    "tsconfig-paths": "^3.14.1",
     "tsutils": "^3.21.0",
     "typescript": "~4.5.5"
   },
diff --git a/projects/gmm-lib/src/lib/components/gmm-menu/gmm-menu.component.html b/projects/gmm-lib/src/lib/components/gmm-menu/gmm-menu.component.html
index 48d883e937da9662ff5e3e1dacb8b3cf2e9f1f83..ecea5dff473c17be9d3bd24b1cb3d269393faa81 100644
--- a/projects/gmm-lib/src/lib/components/gmm-menu/gmm-menu.component.html
+++ b/projects/gmm-lib/src/lib/components/gmm-menu/gmm-menu.component.html
@@ -4,6 +4,7 @@
     <mat-button-toggle-group class="float-right" value="gmmGroup">
       <!-- Sort by group -->
       <mat-button-toggle
+        class="gmm-group-toggle"
         aria-label="Sort ground motion models by group"
         [checked]="isGmmGrouped"
         (click)="onListChange(true)"
@@ -16,6 +17,7 @@
 
       <!-- Sort alphabetically-->
       <mat-button-toggle
+        class="gmm-alpha-toggle"
         aria-label="Sort ground motion models alphabetically"
         [checked]="!isGmmGrouped"
         (click)="onListChange(false)"
diff --git a/projects/hazard-lib/src/lib/components/site-class-form/site-class-form.component.html b/projects/hazard-lib/src/lib/components/site-class-form/site-class-form.component.html
index 3f4ee9098ea4dfba75d5f81f57a73cbb77b8e745..35a7fc6f5481684fe4257dbe906dcabbce2c1637 100644
--- a/projects/hazard-lib/src/lib/components/site-class-form/site-class-form.component.html
+++ b/projects/hazard-lib/src/lib/components/site-class-form/site-class-form.component.html
@@ -1,7 +1,9 @@
 <mat-form-field [class]="formFieldClass" *ngIf="controlState">
   <mat-label> {{ label }} </mat-label>
   <mat-select [ngrxFormControlState]="controlState">
-    <mat-option [value]="siteClassPlaceHolder.value">{{ siteClassPlaceHolder.display }}</mat-option>
+    <mat-option [value]="siteClassPlaceHolder.value" *ngIf="siteClasses?.length === 0">
+      {{ siteClassPlaceHolder.display }}
+    </mat-option>
     <mat-option *ngFor="let siteClass of siteClasses" [value]="siteClass.value">
       {{ siteClass.display }}
     </mat-option>
diff --git a/projects/hazard-lib/src/lib/components/site-class-form/site-class-form.component.ts b/projects/hazard-lib/src/lib/components/site-class-form/site-class-form.component.ts
index 85d1d11eae458de14af83ae2758d56c60d5aaa6e..66b728a8e35f53c8d5358ebaf5b5b7e61933719e 100644
--- a/projects/hazard-lib/src/lib/components/site-class-form/site-class-form.component.ts
+++ b/projects/hazard-lib/src/lib/components/site-class-form/site-class-form.component.ts
@@ -21,7 +21,7 @@ export class SiteClassFormComponent {
   label = 'Site Class';
 
   @Input()
-  siteClasses: nshmpWsUtils.metadata.Parameter[];
+  siteClasses: nshmpWsUtils.metadata.Parameter[] = [];
 
   constructor() {}
 }
diff --git a/projects/hazard-lib/src/lib/models/response-spectra.model.ts b/projects/hazard-lib/src/lib/models/response-spectra.model.ts
index ee99ed902ea540ebecdf8db69d146116a18d5faf..5c9b8150a6685c33b7150bccd46b491bf6789e4a 100644
--- a/projects/hazard-lib/src/lib/models/response-spectra.model.ts
+++ b/projects/hazard-lib/src/lib/models/response-spectra.model.ts
@@ -4,7 +4,7 @@ import {nshmpLib, nshmpWsUtils} from '@ghsc/nshmp-utils';
  * Response spectra wrapper for each site class
  */
 export interface ResponseSpectra {
-  siteClass: nshmpWsUtils.metadata.EnumParameterValues;
+  siteClass: string;
   imts: nshmpLib.Imt[];
   responseSpectrum: ResponseSpectrum[];
 }
diff --git a/projects/nshmp-apps/cypress.json b/projects/nshmp-apps/cypress.json
deleted file mode 100644
index 775c1f6f0ab19bfeb453478176b252f6fcc83972..0000000000000000000000000000000000000000
--- a/projects/nshmp-apps/cypress.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
-  "fileServerFolder": ".",
-  "fixturesFolder": "./cypress/fixtures",
-  "integrationFolder": "./cypress/integration",
-  "pluginsFile": "./cypress/plugins/index",
-  "supportFile": false,
-  "video": false,
-  "videosFolder": "../../dist/cypress/nshmp-apps/videos",
-  "screenshotsFolder": "../../dist/cypress/nshmp-apps/screenshots",
-  "chromeWebSecurity": false
-}
diff --git a/projects/nshmp-apps/cypress/config/cypress-all.json b/projects/nshmp-apps/cypress/config/cypress-all.json
new file mode 100644
index 0000000000000000000000000000000000000000..bf2840ebd41fe9628af5acf1968c2740d6a31eb9
--- /dev/null
+++ b/projects/nshmp-apps/cypress/config/cypress-all.json
@@ -0,0 +1,12 @@
+{
+  "chromeWebSecurity": false,
+  "downloadsFolder": "../../../../dist/cypress/nshmp-apps/downloads",
+  "fileServerFolder": ".",
+  "fixturesFolder": "../fixtures",
+  "integrationFolder": "../integration",
+  "pluginsFile": "../plugins/index",
+  "screenshotsFolder": "../../../../dist/cypress/nshmp-apps/screenshots",
+  "supportFile": false,
+  "video": true,
+  "videosFolder": "../../../../dist/cypress/nshmp-apps/videos"
+}
diff --git a/projects/nshmp-apps/cypress/config/cypress-dashboard.json b/projects/nshmp-apps/cypress/config/cypress-dashboard.json
new file mode 100644
index 0000000000000000000000000000000000000000..9b4571f9f7f6101eb4fa9d54a69f862a253fd64f
--- /dev/null
+++ b/projects/nshmp-apps/cypress/config/cypress-dashboard.json
@@ -0,0 +1,12 @@
+{
+  "chromeWebSecurity": false,
+  "downloadsFolder": "../../../../dist/cypress/nshmp-apps/downloads",
+  "fileServerFolder": ".",
+  "fixturesFolder": "../fixtures",
+  "integrationFolder": "../integration/dashboard",
+  "pluginsFile": "../plugins/index",
+  "screenshotsFolder": "../../../../dist/cypress/nshmp-apps/screenshots",
+  "supportFile": false,
+  "video": true,
+  "videosFolder": "../../../../dist/cypress/nshmp-apps/videos"
+}
diff --git a/projects/nshmp-apps/cypress/config/cypress-dev.json b/projects/nshmp-apps/cypress/config/cypress-dev.json
new file mode 100644
index 0000000000000000000000000000000000000000..b868a56582b9903640668fd60b222abd34e26c77
--- /dev/null
+++ b/projects/nshmp-apps/cypress/config/cypress-dev.json
@@ -0,0 +1,12 @@
+{
+  "chromeWebSecurity": false,
+  "downloadsFolder": "../../../../dist/cypress/nshmp-apps/downloads",
+  "fileServerFolder": ".",
+  "fixturesFolder": "../fixtures",
+  "integrationFolder": "../integration/dev",
+  "pluginsFile": "../plugins/index",
+  "screenshotsFolder": "../../../../dist/cypress/nshmp-apps/screenshots",
+  "supportFile": false,
+  "video": true,
+  "videosFolder": "../../../../dist/cypress/nshmp-apps/videos"
+}
diff --git a/projects/nshmp-apps/cypress/config/cypress-gmm.json b/projects/nshmp-apps/cypress/config/cypress-gmm.json
new file mode 100644
index 0000000000000000000000000000000000000000..34ea708d8c95bcfd17ed10b145383a44ea95b15b
--- /dev/null
+++ b/projects/nshmp-apps/cypress/config/cypress-gmm.json
@@ -0,0 +1,12 @@
+{
+  "chromeWebSecurity": false,
+  "downloadsFolder": "../../../../dist/cypress/nshmp-apps/downloads",
+  "fileServerFolder": ".",
+  "fixturesFolder": "../fixtures",
+  "integrationFolder": "../integration/gmm",
+  "pluginsFile": "../plugins/index",
+  "screenshotsFolder": "../../../../dist/cypress/nshmp-apps/screenshots",
+  "supportFile": false,
+  "video": true,
+  "videosFolder": "../../../../dist/cypress/nshmp-apps/videos"
+}
diff --git a/projects/nshmp-apps/cypress/config/cypress-hazard.json b/projects/nshmp-apps/cypress/config/cypress-hazard.json
new file mode 100644
index 0000000000000000000000000000000000000000..78d7af41a3b5bc489470bb78f3ed0b04a3525132
--- /dev/null
+++ b/projects/nshmp-apps/cypress/config/cypress-hazard.json
@@ -0,0 +1,12 @@
+{
+  "chromeWebSecurity": false,
+  "downloadsFolder": "../../../../dist/cypress/nshmp-apps/downloads",
+  "fileServerFolder": ".",
+  "fixturesFolder": "../fixtures",
+  "integrationFolder": "../integration/hazard",
+  "pluginsFile": "../plugins/index",
+  "screenshotsFolder": "../../../../dist/cypress/nshmp-apps/screenshots",
+  "supportFile": false,
+  "video": true,
+  "videosFolder": "../../../../dist/cypress/nshmp-apps/videos"
+}
diff --git a/projects/nshmp-apps/cypress/config/cypress-services.json b/projects/nshmp-apps/cypress/config/cypress-services.json
new file mode 100644
index 0000000000000000000000000000000000000000..c8aab1ef8d6784e6a582fe5dec37dc414f44c0ad
--- /dev/null
+++ b/projects/nshmp-apps/cypress/config/cypress-services.json
@@ -0,0 +1,12 @@
+{
+  "chromeWebSecurity": false,
+  "downloadsFolder": "../../../../dist/cypress/nshmp-apps/downloads",
+  "fileServerFolder": ".",
+  "fixturesFolder": "../fixtures",
+  "integrationFolder": "../integration/services",
+  "pluginsFile": "../plugins/index",
+  "screenshotsFolder": "../../../../dist/cypress/nshmp-apps/screenshots",
+  "supportFile": false,
+  "video": true,
+  "videosFolder": "../../../../dist/cypress/nshmp-apps/videos"
+}
diff --git a/projects/nshmp-apps/cypress/config/cypress-source-models.json b/projects/nshmp-apps/cypress/config/cypress-source-models.json
new file mode 100644
index 0000000000000000000000000000000000000000..f912962f6308f912c68e0a2b5fdee1b842e4050a
--- /dev/null
+++ b/projects/nshmp-apps/cypress/config/cypress-source-models.json
@@ -0,0 +1,12 @@
+{
+  "chromeWebSecurity": false,
+  "downloadsFolder": "../../../../dist/cypress/nshmp-apps/downloads",
+  "fileServerFolder": ".",
+  "fixturesFolder": "../fixtures",
+  "integrationFolder": "../integration/source-models",
+  "pluginsFile": "../plugins/index",
+  "screenshotsFolder": "../../../../dist/cypress/nshmp-apps/screenshots",
+  "supportFile": false,
+  "video": true,
+  "videosFolder": "../../../../dist/cypress/nshmp-apps/videos"
+}
diff --git a/projects/nshmp-apps/cypress/fixtures/aws-auth.json b/projects/nshmp-apps/cypress/fixtures/aws-auth.json
new file mode 100644
index 0000000000000000000000000000000000000000..46217f2240d7e47741267b086df98e75cced48bb
--- /dev/null
+++ b/projects/nshmp-apps/cypress/fixtures/aws-auth.json
@@ -0,0 +1,3 @@
+{
+  "isAuthorized": true
+}
diff --git a/projects/nshmp-apps/cypress/integration/dashboard/dashboard.spec.ts b/projects/nshmp-apps/cypress/integration/dashboard/dashboard.spec.ts
index b9818827bba07c971ca2b92859f78fbafb66b7be..ae36596bad8cf2cb9b3f69be5f622dc895a77a23 100644
--- a/projects/nshmp-apps/cypress/integration/dashboard/dashboard.spec.ts
+++ b/projects/nshmp-apps/cypress/integration/dashboard/dashboard.spec.ts
@@ -1,11 +1,25 @@
+import {navigation} from '../../../../nshmp-lib/src/lib/utils/navigation.utils';
+import * as utils from '../../utils';
+
 describe('Dashboard', () => {
   beforeEach(() => {
+    cy.viewport('macbook-16');
     cy.visit('/');
   });
 
   describe('Check nshmp-template', () => {
-    it('Has nshmp-template', () => {
-      cy.get('nshmp-template').should('exist');
+    utils.hasNshmpTemplate(navigation());
+  });
+
+  describe('Check dashboard', () => {
+    const nav = navigation().map(navItem => {
+      const list = navItem.navigation.filter(app => app.display !== 'Dashboard');
+      return {
+        ...navItem,
+        navigation: list,
+      };
     });
+
+    utils.hasDashboard(nav);
   });
 });
diff --git a/projects/nshmp-apps/cypress/integration/dev/dashboard/dev-dashboard.spec.ts b/projects/nshmp-apps/cypress/integration/dev/dashboard/dev-dashboard.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..39a66feab2bf7b85c9a4588e55866b54dd4c5e94
--- /dev/null
+++ b/projects/nshmp-apps/cypress/integration/dev/dashboard/dev-dashboard.spec.ts
@@ -0,0 +1,25 @@
+import {devNavigation} from '../../../../../nshmp-lib/src/lib/utils/navigation.utils';
+import * as utils from '../../../utils';
+
+describe('Development Dashboard', () => {
+  beforeEach(() => {
+    cy.viewport('macbook-16');
+    cy.visit('/dev');
+  });
+
+  describe('Check nshmp-template', () => {
+    utils.hasNshmpTemplate(devNavigation());
+  });
+
+  describe('Check dev dashboard', () => {
+    const nav = devNavigation().map(navItem => {
+      const list = navItem.navigation.filter(app => app.display !== 'Development Dashboard');
+      return {
+        ...navItem,
+        navigation: list,
+      };
+    });
+
+    utils.hasDashboard(nav);
+  });
+});
diff --git a/projects/nshmp-apps/cypress/integration/dev/math/exceedance-explorer/exceedance-explorer.spec.ts b/projects/nshmp-apps/cypress/integration/dev/math/exceedance-explorer/exceedance-explorer.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..66e242f39b230563901ef0f482e50ea4d3bed264
--- /dev/null
+++ b/projects/nshmp-apps/cypress/integration/dev/math/exceedance-explorer/exceedance-explorer.spec.ts
@@ -0,0 +1,56 @@
+import {devNavigation} from '../../../../../../nshmp-lib/src/lib/utils/navigation.utils';
+import * as utils from '../../../../utils';
+
+describe('Exceedance Explorer Application', () => {
+  beforeEach(() => {
+    cy.viewport('macbook-16');
+    cy.visit('/dev/math/exceedance-explorer');
+  });
+
+  describe('Check nshmp-template', () => {
+    utils.hasNshmpTemplate(devNavigation());
+  });
+
+  describe('Application', () => {
+    utils.hasControlPanel();
+    utils.hasPlotContent();
+    utils.toggleControlPanel();
+    utils.togglePlotPanel();
+    utils.toggleSettingPanel();
+    utils.changePlotSettings('.exceedance-plot', '.exceedance-settings');
+
+    it('Has default plot', () => {
+      cy.get('nshmp-template-plot-content').find('plotly-plot').should('be.visible');
+    });
+
+    it('Has control panel form fields', () => {
+      setControlPanel();
+    });
+
+    it('Adds and removes curve from plot', () => {
+      const buttons: utils.ControlPanelButtons = {
+        hasResetButton: true,
+        hasServiceCallButton: false,
+        hasSubmitButton: true,
+      };
+      utils.controlPanelButtonsNotEnabled(buttons);
+
+      setControlPanel();
+
+      utils.submitFormCheckPlot({
+        plotClass: '.exceedance-plot',
+        intercept: false,
+      });
+
+      utils.controlPanelButtonsEnabled(buttons);
+    });
+  });
+});
+
+function setControlPanel() {
+  utils.setInput('.median-input', '2.0');
+  utils.setInput('.sigma-input', '0.5');
+  utils.setInput('.rate-input', '1.0');
+  cy.get('.truncation-checkbox').find('input').should('be.checked');
+  utils.setInput('.truncation-level-input', '3.0');
+}
diff --git a/projects/nshmp-apps/cypress/integration/gmm/distance/gmm-distance.spec.ts b/projects/nshmp-apps/cypress/integration/gmm/distance/gmm-distance.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a393697edef6262e975b1e641d8113db87e555be
--- /dev/null
+++ b/projects/nshmp-apps/cypress/integration/gmm/distance/gmm-distance.spec.ts
@@ -0,0 +1,126 @@
+import {navigation} from '../../../../../nshmp-lib/src/lib/utils/navigation.utils';
+import * as utils from '../../../utils';
+
+const url = 'https://earthquake.usgs.gov/ws/nshmp/data/gmm/distance';
+
+describe('Ground Motion vs. Distance Application', () => {
+  beforeEach(() => {
+    cy.viewport('macbook-16');
+    cy.intercept(url).as('usage');
+    cy.visit('/gmm/distance');
+    cy.wait('@usage');
+  });
+
+  describe('Check nshmp-template', () => {
+    utils.hasNshmpTemplate(navigation());
+  });
+
+  describe('Application', () => {
+    utils.hasControlPanel();
+    utils.hasPlotContent();
+    utils.toggleControlPanel();
+    utils.togglePlotPanel();
+    utils.toggleSettingPanel();
+    utils.changePlotSettings('.distance-plot', '.distance-settings');
+    utils.hasGmmMenu();
+
+    it('Can set GMM menus', () => {
+      setGmmMenus();
+    });
+
+    it('Can set event parameters', () => {
+      setEventParameters();
+    });
+
+    it('Can set geometry parameters', () => {
+      utils.setSourceGeometryParameters();
+    });
+
+    it('Can set site parameters', () => {
+      utils.setSiteParameters();
+    });
+
+    it('Calls service and plots response', () => {
+      const buttons: utils.ControlPanelButtons = {
+        hasResetButton: false,
+        hasServiceCallButton: true,
+        hasSubmitButton: true,
+      };
+      utils.controlPanelButtonsNotEnabled(buttons);
+
+      setControlPanel();
+
+      utils.submitFormCheckPlot({
+        plotClass: '.distance-plot',
+      });
+
+      utils.controlPanelButtonsEnabled(buttons);
+    });
+
+    describe('Application Tabs', () => {
+      it('Has plot tab', () => {
+        cy.get('plotly-plot');
+
+        utils.hasApplicationTab('.medians-tab');
+
+        cy.get('plotly-plot').should('not.exist');
+
+        utils.hasApplicationTab('.plots-tab');
+
+        cy.get('plotly-plot');
+      });
+
+      it('Has medians tab', () => {
+        cy.get('plotly-plot');
+
+        utils.exportDataTableNotExist();
+        utils.dataTableNotExist();
+
+        setControlPanel();
+
+        utils.submitFormCheckPlot({
+          plotClass: '.distance-plot',
+        });
+
+        utils.hasApplicationTab('.medians-tab');
+
+        cy.get('plotly-plot').should('not.exist');
+
+        utils.hasExportDataTable('gmm-distance-means.csv');
+        utils.hasDataTable();
+      });
+    });
+  });
+});
+
+function setControlPanel() {
+  setGmmMenus();
+  setEventParameters();
+  utils.setSourceGeometryParameters();
+  utils.setSiteParameters();
+}
+
+function setGmmMenus() {
+  cy.get('nshmp-template-control-panel').find('form').should('be.visible').as('form');
+
+  cy.get('@form').find('.multi-select-parameter-menu').click();
+  utils.selectMatOption('gmm');
+
+  cy.get('@form').find('.imt-select').find('mat-select').click();
+  utils.selectMatOption('default');
+
+  cy.get('@form').find('gmm-lib-gmm-menu').find('select').select([0, 1, 2, 3, 4]);
+
+  cy.get('@form')
+    .find('.imt-select')
+    .find('mat-select')
+    .click()
+    .get('.cdk-overlay-container')
+    .find('mat-option')
+    .should('not.have.value', 'default');
+  utils.selectMatOption('PGA');
+}
+
+function setEventParameters() {
+  utils.setMagnitude();
+}
diff --git a/projects/nshmp-apps/cypress/integration/gmm/magnitude/gmm-magnitude.spec.ts b/projects/nshmp-apps/cypress/integration/gmm/magnitude/gmm-magnitude.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c28bbad1acdd881dbfc69aef9e8ebc7c7aeb79cd
--- /dev/null
+++ b/projects/nshmp-apps/cypress/integration/gmm/magnitude/gmm-magnitude.spec.ts
@@ -0,0 +1,190 @@
+import {navigation} from '../../../../../nshmp-lib/src/lib/utils/navigation.utils';
+import * as utils from '../../../utils';
+
+const url = 'https://earthquake.usgs.gov/ws/nshmp/data/gmm/magnitude';
+
+describe('Ground Motion vs. Magnitude', () => {
+  beforeEach(() => {
+    cy.viewport('macbook-16');
+    cy.intercept(url).as('usage');
+    cy.visit('/gmm/magnitude');
+    cy.wait('@usage');
+  });
+
+  describe('Check nshmp-template', () => {
+    utils.hasNshmpTemplate(navigation());
+  });
+
+  describe('Application', () => {
+    utils.hasControlPanel();
+    utils.hasPlotContent();
+    utils.toggleControlPanel();
+    utils.togglePlotPanel();
+    utils.toggleSettingPanel();
+    utils.changePlotSettings('.mean-plot', '.mean-settings');
+    utils.changePlotSettings('.sigma-plot', '.sigma-settings');
+    utils.hasGmmMenu();
+
+    it('Can set GMM menus', () => {
+      setGmmMenus();
+    });
+
+    it('Can set event parameters', () => {
+      setEventParameters();
+    });
+
+    it('Can set geometry parameters', () => {
+      utils.setSourceGeometryParameters();
+    });
+
+    it('Can set site parameters', () => {
+      utils.setSiteParameters();
+    });
+
+    it('Can set path parameters', () => {
+      setPathParameters();
+    });
+
+    it('Can reset control panel inputs', () => {
+      utils.canResetControlPanel(() => setControlPanel());
+    });
+
+    it('Calls service and plots response', () => {
+      const buttons: utils.ControlPanelButtons = {
+        hasResetButton: false,
+        hasServiceCallButton: true,
+        hasSubmitButton: true,
+      };
+      utils.controlPanelButtonsNotEnabled(buttons);
+
+      setControlPanel();
+      utils.submitFormCheckPlots({
+        firstPlotClass: '.mean-plot',
+        secondPlotClass: '.sigma-plot',
+      });
+
+      utils.controlPanelButtonsEnabled(buttons);
+    });
+
+    describe('Application Tabs', () => {
+      it('Has plot tab', () => {
+        cy.get('plotly-plot');
+
+        utils.hasApplicationTab('.medians-tab');
+
+        cy.get('plotly-plot').should('not.exist');
+
+        utils.hasApplicationTab('.plots-tab');
+
+        cy.get('plotly-plot');
+      });
+
+      it('Has medians tab', () => {
+        cy.get('plotly-plot');
+
+        utils.exportDataTableNotExist();
+        utils.dataTableNotExist();
+
+        setControlPanel();
+
+        utils.submitFormCheckPlots({
+          firstPlotClass: '.mean-plot',
+          secondPlotClass: '.sigma-plot',
+        });
+
+        utils.hasApplicationTab('.medians-tab');
+
+        cy.get('plotly-plot').should('not.exist');
+
+        utils.hasExportDataTable('gmm-magnitude-means.csv');
+        utils.hasDataTable();
+      });
+
+      it('Has sigmas tab', () => {
+        cy.get('plotly-plot');
+
+        utils.exportDataTableNotExist();
+        utils.dataTableNotExist();
+
+        setControlPanel();
+
+        utils.submitFormCheckPlots({
+          firstPlotClass: '.mean-plot',
+          secondPlotClass: '.sigma-plot',
+        });
+
+        utils.hasApplicationTab('.sigmas-tab');
+
+        cy.get('plotly-plot').should('not.exist');
+
+        utils.hasExportDataTable('gmm-magnitude-sigmas.csv');
+        utils.hasDataTable();
+      });
+    });
+  });
+});
+
+function setControlPanel() {
+  setGmmMenus();
+  setEventParameters();
+  utils.setSourceGeometryParameters();
+  setPathParameters();
+  utils.setSiteParameters();
+}
+
+function setGmmMenus() {
+  cy.get('nshmp-template-control-panel').find('form').should('be.visible').as('form');
+
+  cy.get('@form').find('.multi-select-parameter-menu').click();
+  utils.selectMatOption('gmm');
+
+  cy.get('@form').find('.imt-select').find('mat-select').click();
+  utils.selectMatOption('default');
+
+  cy.get('@form').find('gmm-lib-gmm-menu').find('select').select([0, 1, 2, 3, 4]);
+
+  cy.get('@form')
+    .find('.imt-select')
+    .find('mat-select')
+    .click()
+    .get('.cdk-overlay-container')
+    .find('mat-option')
+    .should('not.have.value', 'default');
+  utils.selectMatOption('PGA');
+}
+
+function setPathParameters() {
+  cy.get('nshmp-template-control-panel')
+    .find('.distance-input')
+    .find('input')
+    .should('be.visible')
+    .clear()
+    .type('20.0')
+    .should('have.value', '20.0');
+}
+
+function setEventParameters() {
+  cy.get('nshmp-template-control-panel')
+    .find('.mmin-input')
+    .find('input')
+    .should('be.visible')
+    .clear()
+    .type('4.0')
+    .should('have.value', '4.0');
+
+  cy.get('nshmp-template-control-panel')
+    .find('.mmax-input')
+    .find('input')
+    .should('be.visible')
+    .clear()
+    .type('7.5')
+    .should('have.value', '7.5');
+
+  cy.get('nshmp-template-control-panel')
+    .find('.mstep-input')
+    .find('input')
+    .should('be.visible')
+    .clear()
+    .type('0.1')
+    .should('have.value', '0.1');
+}
diff --git a/projects/nshmp-apps/cypress/integration/gmm/spectra/response-spectra.spec.ts b/projects/nshmp-apps/cypress/integration/gmm/spectra/response-spectra.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2c316034f23b3282279aba2785f054673ac1d984
--- /dev/null
+++ b/projects/nshmp-apps/cypress/integration/gmm/spectra/response-spectra.spec.ts
@@ -0,0 +1,176 @@
+import {navigation} from '../../../../../nshmp-lib/src/lib/utils/navigation.utils';
+import * as utils from '../../../utils';
+
+const url = 'https://earthquake.usgs.gov/ws/nshmp/data/gmm/spectra';
+
+describe('Response Spectra', () => {
+  beforeEach(() => {
+    cy.viewport('macbook-16');
+    cy.intercept(url).as('usage');
+    cy.visit('/gmm/spectra');
+    cy.wait('@usage');
+  });
+
+  // describe('Check nshmp-template', () => {
+  //   utils.hasNshmpTemplate(navigation());
+  // });
+
+  describe('Application', () => {
+    //   utils.hasControlPanel();
+    //   utils.hasPlotContent();
+    //   utils.toggleControlPanel();
+    //   utils.togglePlotPanel();
+    //   utils.toggleSettingPanel();
+    //   utils.changePlotSettings('.spectra-plot', '.spectra-settings');
+    //   utils.changePlotSettings('.sigma-plot', '.sigma-settings');
+    //   utils.hasGmmMenu();
+
+    //   it('Can set GMM menus', () => {
+    //     setGmmMenus();
+    //   });
+
+    //   it('Can set event parameters', () => {
+    //     setEventParameters();
+    //   });
+
+    //   it('Can set geometry parameters', () => {
+    //     utils.setSourceGeometryParameters();
+    //   });
+
+    //   it('Can set site parameters', () => {
+    //     utils.setSiteParameters();
+    //   });
+
+    //   it('Can set path parameters', () => {
+    //     setPathParameters();
+    //   });
+
+    //   it('Can reset control panel inputs', () => {
+    //     utils.canResetControlPanel(() => setControlPanel());
+    //   });
+
+    //   it('Calls service and plots response', () => {
+    //     const buttons: utils.ControlPanelButtons = {
+    //       hasResetButton: false,
+    //       hasServiceCallButton: true,
+    //       hasSubmitButton: true,
+    //     };
+    //     utils.controlPanelButtonsNotEnabled(buttons);
+
+    //     setControlPanel();
+    //     utils.submitFormCheckPlots({
+    //       firstPlotClass: '.spectra-plot',
+    //       secondPlotClass: '.sigma-plot',
+    //     });
+
+    //     utils.controlPanelButtonsEnabled(buttons);
+    //   });
+
+    describe('Application Tabs', () => {
+      it('Has plot tab', () => {
+        cy.get('plotly-plot');
+
+        utils.hasApplicationTab('.medians-tab');
+
+        cy.get('plotly-plot').should('not.exist');
+
+        utils.hasApplicationTab('.plots-tab');
+
+        cy.get('plotly-plot');
+      });
+
+      it('Has medians tab', () => {
+        cy.get('plotly-plot');
+
+        utils.exportDataTableNotExist();
+        utils.dataTableNotExist();
+
+        setControlPanel();
+
+        utils.submitFormCheckPlots({
+          firstPlotClass: '.spectra-plot',
+          secondPlotClass: '.sigma-plot',
+        });
+
+        utils.hasApplicationTab('.medians-tab');
+
+        cy.get('plotly-plot').should('not.exist');
+
+        utils.hasExportDataTable('spectra-medians.csv');
+        utils.hasDataTable();
+      });
+
+      it('Has sigmas tab', () => {
+        cy.get('plotly-plot');
+
+        utils.exportDataTableNotExist();
+        utils.dataTableNotExist();
+
+        setControlPanel();
+
+        utils.submitFormCheckPlots({
+          firstPlotClass: '.spectra-plot',
+          secondPlotClass: '.sigma-plot',
+        });
+
+        utils.hasApplicationTab('.sigmas-tab');
+
+        cy.get('plotly-plot').should('not.exist');
+
+        utils.hasExportDataTable('spectra-sigmas.csv');
+        utils.hasDataTable();
+      });
+    });
+  });
+});
+
+function setControlPanel() {
+  setGmmMenus();
+  setEventParameters();
+  utils.setSourceGeometryParameters();
+  setPathParameters();
+  utils.setSiteParameters();
+}
+
+function setGmmMenus() {
+  cy.get('nshmp-template-control-panel').find('form').should('be.visible').as('form');
+
+  cy.get('@form').find('.multi-select-parameter-menu').click();
+  utils.selectMatOption('gmm');
+
+  cy.get('@form').find('gmm-lib-gmm-menu').find('select').select([0, 1, 2, 3, 4]);
+}
+
+function setPathParameters() {
+  cy.get('nshmp-template-control-panel')
+    .find('.rx-input')
+    .find('input')
+    .should('be.visible')
+    .clear()
+    .type('20.0')
+    .should('have.value', '20.0');
+
+  cy.get('nshmp-template-control-panel')
+    .find('.rrup-input')
+    .find('input')
+    .should('be.visible')
+    .should('not.be.enabled');
+
+  cy.get('nshmp-template-control-panel')
+    .find('.rjb-input')
+    .find('input')
+    .should('be.visible')
+    .should('not.be.enabled');
+}
+
+function setEventParameters() {
+  utils.setMagnitude();
+
+  cy.get('nshmp-template-control-panel')
+    .find('.rake-input')
+    .find('input')
+    .should('be.visible')
+    .clear()
+    .type('90')
+    .should('have.value', '90');
+}
diff --git a/projects/nshmp-apps/cypress/integration/hazard/disagg/disagg.spec.ts b/projects/nshmp-apps/cypress/integration/hazard/disagg/disagg.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1f7888f8e94053782965da1a90789d7f9aca1919
--- /dev/null
+++ b/projects/nshmp-apps/cypress/integration/hazard/disagg/disagg.spec.ts
@@ -0,0 +1,132 @@
+import {navigation} from '../../../../../nshmp-lib/src/lib/utils/navigation.utils';
+import * as utils from '../../../utils';
+
+const url = 'https://earthquake.usgs.gov/ws/nshmp/*/dynamic/disagg';
+
+describe('Disagg Application', () => {
+  beforeEach(() => {
+    cy.viewport('macbook-16');
+    cy.intercept(url).as('usage');
+    cy.visit('/hazard/disagg');
+    cy.wait('@usage');
+  });
+
+  describe('Check nshmp-template', () => {
+    utils.hasNshmpTemplate(navigation());
+  });
+
+  describe('Application', () => {
+    utils.hasControlPanel();
+    utils.hasPlotContent();
+    utils.toggleControlPanel();
+    utils.togglePlotPanel();
+    utils.hasHazardModelMenu();
+
+    it('Can set control panel', () => {
+      setControlPanel();
+    });
+
+    it('Downloads data as csv', () => {
+      setControlPanel();
+      utils.submitFormCheckPlot({
+        plotClass: 'hazard-lib-disagg-plot',
+      });
+
+      cy.get('nshmp-template-plot-content').find('.export-button').scrollIntoView().click();
+      utils.hasDownloadedFile('disagg-Total.csv');
+    });
+
+    it('Downloads summary text', () => {
+      setControlPanel();
+      utils.submitFormCheckPlot({
+        plotClass: 'hazard-lib-disagg-plot',
+      });
+
+      cy.get('nshmp-template-plot-content')
+        .find('.summary')
+        .find('.summary-report')
+        .scrollIntoView()
+        .find('button')
+        .click();
+      utils.hasDownloadedFile('disagg-Total.csv');
+    });
+
+    it('Calls service and plots response', () => {
+      const buttons: utils.ControlPanelButtons = {
+        hasResetButton: false,
+        hasServiceCallButton: true,
+        hasSubmitButton: true,
+      };
+      utils.controlPanelButtonsNotEnabled(buttons);
+
+      setControlPanel();
+
+      cy.get('.cdk-overlay-container').as('overlay');
+
+      cy.get('nshmp-template-plot-content')
+        .find('.component-select')
+        .find('mat-select')
+        .should('be.visible')
+        .click();
+      cy.get('@overlay').find('mat-option').should('not.exist');
+
+      cy.get('nshmp-template-plot-content')
+        .find('.export-button-loading')
+        .should('be.visible')
+        .should('not.be.enabled');
+
+      cy.get('nshmp-template-plot-content')
+        .find('.summary')
+        .find('app-disagg-summary')
+        .scrollIntoView()
+        .should('not.be.visible');
+
+      cy.get('nshmp-template-plot-content')
+        .find('.summary')
+        .find('app-disagg-contributors')
+        .scrollIntoView()
+        .should('not.be.visible');
+
+      utils.submitFormCheckPlot({
+        plotClass: 'hazard-lib-disagg-plot',
+      });
+
+      cy.get('nshmp-template-plot-content')
+        .find('.component-select')
+        .scrollIntoView()
+        .find('mat-select')
+        .should('be.visible')
+        .click();
+      cy.get('@overlay').find('mat-option').should('have.length.above', 1);
+      utils.selectMatOption('Total');
+
+      cy.get('nshmp-template-plot-content')
+        .find('.export-button')
+        .scrollIntoView()
+        .should('be.visible')
+        .should('be.enabled')
+        .click();
+
+      cy.get('nshmp-template-plot-content')
+        .find('.summary')
+        .find('app-disagg-summary')
+        .scrollIntoView()
+        .should('be.visible');
+
+      cy.get('nshmp-template-plot-content')
+        .find('.summary')
+        .find('app-disagg-contributors')
+        .scrollIntoView()
+        .should('be.visible');
+
+      utils.controlPanelButtonsEnabled(buttons);
+    });
+  });
+});
+
+function setControlPanel() {
+  utils.setHazardCommonControlPanel();
+
+  cy.get('nshmp-template-control-panel').find('.imt-select').find('mat-select').click();
+  utils.selectMatOption('PGA');
+}
diff --git a/projects/nshmp-apps/cypress/integration/hazard/dynamic/dynamic-hazard.spec.ts b/projects/nshmp-apps/cypress/integration/hazard/dynamic/dynamic-hazard.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..032ed0c67f391e263fb4bce23a822f513bdb3935
--- /dev/null
+++ b/projects/nshmp-apps/cypress/integration/hazard/dynamic/dynamic-hazard.spec.ts
@@ -0,0 +1,112 @@
+import {navigation} from '../../../../../nshmp-lib/src/lib/utils/navigation.utils';
+import * as utils from '../../../utils';
+
+const url = 'https://earthquake.usgs.gov/ws/nshmp/*/dynamic/hazard';
+
+describe('Dynamic Hazard Application', () => {
+  beforeEach(() => {
+    cy.viewport('macbook-16');
+    cy.intercept(url).as('usage');
+    cy.visit('/hazard/dynamic');
+    cy.wait('@usage');
+  });
+
+  describe('Check nshmp-template', () => {
+    utils.hasNshmpTemplate(navigation());
+  });
+
+  describe('Application', () => {
+    utils.hasControlPanel();
+    utils.hasPlotContent();
+    utils.toggleControlPanel();
+    utils.togglePlotPanel();
+    utils.toggleSettingPanel();
+    utils.hasHazardModelMenu();
+    utils.changePlotSettings('.hazard-plot', '.hazard-settings');
+    utils.changePlotSettings('.spectra-plot', '.spectra-settings');
+
+    it('Can set control panel', () => {
+      setControlPanel();
+    });
+
+    it('Calls service and plots response', () => {
+      const buttons: utils.ControlPanelButtons = {
+        hasResetButton: false,
+        hasServiceCallButton: true,
+        hasSubmitButton: true,
+      };
+      utils.controlPanelButtonsNotEnabled(buttons);
+
+      setControlPanel();
+
+      utils.submitFormCheckPlots({
+        firstPlotClass: '.hazard-plot',
+        secondPlotClass: '.spectra-plot',
+      });
+
+      utils.controlPanelButtonsEnabled(buttons);
+    });
+
+    describe('Application Tabs', () => {
+      it('Has plot tab', () => {
+        cy.get('plotly-plot');
+
+        utils.hasApplicationTab('.hazard-tab');
+
+        cy.get('plotly-plot').should('not.exist');
+
+        utils.hasApplicationTab('.plots-tab');
+
+        cy.get('plotly-plot');
+      });
+
+      it('Has medians tab', () => {
+        cy.get('plotly-plot');
+
+        utils.exportDataTableNotExist();
+        utils.dataTableNotExist();
+
+        setControlPanel();
+
+        utils.submitFormCheckPlots({
+          firstPlotClass: '.hazard-plot',
+          secondPlotClass: '.spectra-plot',
+        });
+
+        utils.hasApplicationTab('.hazard-tab');
+
+        cy.get('plotly-plot').should('not.exist');
+
+        utils.hasExportDataTable('hazard-curves-total-760.csv');
+        utils.hasDataTable();
+      });
+
+      it('Has spectra tab', () => {
+        cy.get('plotly-plot');
+
+        utils.exportDataTableNotExist();
+        utils.dataTableNotExist();
+
+        setControlPanel();
+
+        utils.submitFormCheckPlots({
+          firstPlotClass: '.hazard-plot',
+          secondPlotClass: '.spectra-plot',
+        });
+
+        utils.hasApplicationTab('.spectra-tab');
+
+        cy.get('plotly-plot').should('not.exist');
+
+        utils.hasExportDataTable('response-spectra-total-760.csv');
+        utils.hasDataTable();
+      });
+    });
+  });
+});
+
+function setControlPanel() {
+  utils.setHazardCommonControlPanel();
+  utils.setHazardTruncate();
+  utils.setHazardMaxDirection();
+}
diff --git a/projects/nshmp-apps/cypress/integration/hazard/static/static-hazard.spec.ts b/projects/nshmp-apps/cypress/integration/hazard/static/static-hazard.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..84352a15b509de2c97c9fd39a5881257087932e6
--- /dev/null
+++ b/projects/nshmp-apps/cypress/integration/hazard/static/static-hazard.spec.ts
@@ -0,0 +1,112 @@
+import {navigation} from '../../../../../nshmp-lib/src/lib/utils/navigation.utils';
+import * as utils from '../../../utils';
+
+const url = 'https://**/ws/nshmp/*/static/hazard';
+
+describe('Static Hazard Application', () => {
+  beforeEach(() => {
+    cy.viewport('macbook-16');
+    cy.intercept(url).as('usage');
+    cy.visit('/hazard/static');
+    cy.wait('@usage');
+  });
+
+  describe('Check nshmp-template', () => {
+    utils.hasNshmpTemplate(navigation());
+  });
+
+  describe('Application', () => {
+    utils.hasControlPanel();
+    utils.hasPlotContent();
+    utils.toggleControlPanel();
+    utils.togglePlotPanel();
+    utils.toggleSettingPanel();
+    utils.hasHazardModelMenu();
+    utils.changePlotSettings('.hazard-plot', '.hazard-settings');
+    utils.changePlotSettings('.spectra-plot', '.spectra-settings');
+
+    it('Can set control panel', () => {
+      setControlPanel();
+    });
+
+    it('Calls service and plots response', () => {
+      const buttons: utils.ControlPanelButtons = {
+        hasResetButton: false,
+        hasServiceCallButton: true,
+        hasSubmitButton: true,
+      };
+      utils.controlPanelButtonsNotEnabled(buttons);
+
+      setControlPanel();
+
+      utils.submitFormCheckPlots({
+        firstPlotClass: '.hazard-plot',
+        secondPlotClass: '.spectra-plot',
+      });
+
+      utils.controlPanelButtonsEnabled(buttons);
+    });
+
+    describe('Application Tabs', () => {
+      it('Has plot tab', () => {
+        cy.get('plotly-plot');
+
+        utils.hasApplicationTab('.hazard-tab');
+
+        cy.get('plotly-plot').should('not.exist');
+
+        utils.hasApplicationTab('.plots-tab');
+
+        cy.get('plotly-plot');
+      });
+
+      it('Has medians tab', () => {
+        cy.get('plotly-plot');
+
+        utils.exportDataTableNotExist();
+        utils.dataTableNotExist();
+
+        setControlPanel();
+
+        utils.submitFormCheckPlots({
+          firstPlotClass: '.hazard-plot',
+          secondPlotClass: '.spectra-plot',
+        });
+
+        utils.hasApplicationTab('.hazard-tab');
+
+        cy.get('plotly-plot').should('not.exist');
+
+        utils.hasExportDataTable('static-hazard-curves-CONUS_2018-(-118,34).csv');
+        utils.hasDataTable();
+      });
+
+      it('Has spectra tab', () => {
+        cy.get('plotly-plot');
+
+        utils.exportDataTableNotExist();
+        utils.dataTableNotExist();
+
+        setControlPanel();
+
+        utils.submitFormCheckPlots({
+          firstPlotClass: '.hazard-plot',
+          secondPlotClass: '.spectra-plot',
+        });
+
+        utils.hasApplicationTab('.spectra-tab');
+
+        cy.get('plotly-plot').should('not.exist');
+
+        utils.hasExportDataTable('static-hazard-spectra-CONUS_2018-(-118,34).csv');
+        utils.hasDataTable();
+      });
+    });
+  });
+});
+
+function setControlPanel() {
+  utils.setHazardCommonControlPanel();
+  utils.setHazardMaxDirection();
+  utils.setHazardTruncate();
+}
diff --git a/projects/nshmp-apps/cypress/integration/services/services.spec.ts b/projects/nshmp-apps/cypress/integration/services/services.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..35af555cd8c4f51c78b7dba4346ac375c41efbc8
--- /dev/null
+++ b/projects/nshmp-apps/cypress/integration/services/services.spec.ts
@@ -0,0 +1,35 @@
+import {navigation} from '../../../../nshmp-lib/src/lib/utils/navigation.utils';
+import * as utils from '../../utils';
+
+describe('Services Application', () => {
+  beforeEach(() => {
+    cy.viewport('macbook-16');
+    cy.visit('/services');
+  });
+
+  describe('Check nshmp-template', () => {
+    utils.hasNshmpTemplate(navigation());
+  });
+
+  describe('Application', () => {
+    it('Has service accordians', () => {
+      cy.get('mat-accordion')
+        .should('be.visible')
+        .should('have.length.above', 1)
+        .find('.mat-expansion-panel-content')
+        .should('have.css', 'visibility', 'hidden');
+    });
+
+    it('Can open service accordions', () => {
+      cy.get('mat-accordion')
+        .should('be.visible')
+        .each(accordian => {
+          cy.wrap(accordian)
+            .click()
+            .find('.mat-expansion-panel-content')
+            .scrollIntoView()
+            .should('have.css', 'visibility', 'visible');
+        });
+    });
+  });
+});
diff --git a/projects/nshmp-apps/cypress/integration/source-models/data/data-mapping.spec.ts b/projects/nshmp-apps/cypress/integration/source-models/data/data-mapping.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1e6ffd76a43fe1748cce9145ac9840a8b32f16bf
--- /dev/null
+++ b/projects/nshmp-apps/cypress/integration/source-models/data/data-mapping.spec.ts
@@ -0,0 +1,54 @@
+import {navigation} from '../../../../../nshmp-lib/src/lib/utils/navigation.utils';
+import * as utils from '../../../utils';
+
+const url = '/ws/nshmp/data/fault-sections';
+
+describe('Data Mapping Application', () => {
+  beforeEach(() => {
+    cy.viewport('macbook-16');
+    cy.intercept(url).as('usage');
+    cy.visit('/source-models/data');
+    cy.wait('@usage');
+  });
+
+  describe('Check nshmp-template', () => {
+    utils.hasNshmpTemplate(navigation());
+  });
+
+  describe('Application', () => {
+    utils.hasControlPanel();
+    utils.hasPlotContent();
+    utils.toggleControlPanel();
+    utils.togglePlotPanel();
+
+    it('Can set control panel', () => {
+      setControlPanel();
+    });
+
+    it('Calls service and plots response', () => {
+      const buttons: utils.ControlPanelButtons = {
+        hasResetButton: false,
+        hasServiceCallButton: false,
+        hasSubmitButton: true,
+      };
+      utils.controlPanelButtonsNotEnabled(buttons);
+
+      setControlPanel();
+
+      utils.submitFormCheckPlot({
+        plotClass: 'mgl-map',
+      });
+
+      utils.controlPanelButtonsEnabled(buttons);
+    });
+  });
+});
+
+function setControlPanel() {
+  cy.get('nshmp-template-control-panel')
+    .find('.fault-section-select')
+    .should('be.visible')
+    .find('mat-select')
+    .click();
+  utils.selectMatOption('ALL');
+}
diff --git a/projects/nshmp-apps/cypress/plugins/index.js b/projects/nshmp-apps/cypress/plugins/index.js
index 3a3f378939e6a87444f402a3ea5f5bebeab5bcd9..db8bc0453d29c8985aef2839d6974374cc57f046 100644
--- a/projects/nshmp-apps/cypress/plugins/index.js
+++ b/projects/nshmp-apps/cypress/plugins/index.js
@@ -1,7 +1,33 @@
 // Plugins enable you to tap into, modify, or extend the internal behavior of Cypress
 // For more info, visit https://on.cypress.io/plugins-api
+
 const webpackPreprocessor = require('@cypress/webpack-preprocessor');
 
 module.exports = on => {
-  on('file:preprocessor', webpackPreprocessor());
+  on(
+    'file:preprocessor',
+    webpackPreprocessor({
+      webpackOptions: {
+        resolve: {
+          extensions: ['.ts', '.tsx', '.js'],
+        },
+        module: {
+          rules: [
+            {
+              test: /\.tsx?$/,
+              loader: 'ts-loader',
+              options: {transpileOnly: true},
+            },
+          ],
+        },
+      },
+    })
+  );
+
+  on('task', {
+    log(message) {
+      console.log(message);
+      return null;
+    },
+  });
 };
diff --git a/projects/nshmp-apps/cypress/tsconfig.json b/projects/nshmp-apps/cypress/tsconfig.json
index ba634f4476704a0e2494654c7bbcc0a2662db02e..7c57a901e0f7806b59aaa5e29d315f88931ab8a5 100644
--- a/projects/nshmp-apps/cypress/tsconfig.json
+++ b/projects/nshmp-apps/cypress/tsconfig.json
@@ -2,7 +2,9 @@
   "extends": "../../../tsconfig.json",
   "include": ["**/*.ts", "plugins/index.js"],
   "compilerOptions": {
+    "target": "es5",
+    "lib": ["ES2018", "dom"],
     "sourceMap": false,
-    "types": ["cypress"]
+    "types": ["cypress", "node"]
   }
 }
diff --git a/projects/nshmp-apps/cypress/utils/dashboard.utils.ts b/projects/nshmp-apps/cypress/utils/dashboard.utils.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0158cac1fba2be501378e4fcaf4cbeef0d510281
--- /dev/null
+++ b/projects/nshmp-apps/cypress/utils/dashboard.utils.ts
@@ -0,0 +1,57 @@
+import {NavigationList} from '@ghsc/nshmp-template';
+
+export function hasDashboard(navList: NavigationList[]) {
+  it('Has nshmp-template main', () => {
+    cy.get('nshmp-template-main').should('be.visible');
+  });
+
+  it('Has background image', () => {
+    cy.get('.dashboard').should(
+      'have.css',
+      'background-image',
+      `url("${Cypress.config().baseUrl}dashboard-background.png")`
+    );
+  });
+
+  it('Has dashboard title', () => {
+    cy.get('.dashboard').find('.dashboard--title').should('be.visible');
+  });
+
+  it('Has dashboard description', () => {
+    cy.get('.dashboard').find('.dashboard--description').should('be.visible');
+  });
+
+  it('Has mat cards for applications', () => {
+    cy.get('.dashboard').find('mat-card').should('have.length.above', 0).should('be.visible');
+  });
+
+  navList.forEach(nav => {
+    if (nav.subHeader) {
+      it(`Has "${nav.subHeader}" application list header`, () => {
+        cy.get('.dashboard').find('.app-section').contains(nav.subHeader);
+      });
+    }
+
+    nav.navigation.forEach(app => {
+      const routerLink = app.routerLink.startsWith('/')
+        ? app.routerLink.substring(1)
+        : app.routerLink;
+
+      it(`Has "${app.display}" application`, () => {
+        if (app.routerLink.includes('/aws/')) {
+          cy.intercept('GET', 'https://earthquake.usgs.gov/ws/nshmp/hazard-runs/auth', {
+            fixture: 'aws-auth.json',
+          });
+        }
+
+        cy.get('.dashboard')
+          .find('mat-card')
+          .contains('mat-card-title', app.display)
+          .parent()
+          .click()
+          .url()
+          .should('contain', `${Cypress.config().baseUrl}${routerLink}`);
+      });
+    });
+  });
+}
diff --git a/projects/nshmp-apps/cypress/utils/gmm-lib.utils.ts b/projects/nshmp-apps/cypress/utils/gmm-lib.utils.ts
new file mode 100644
index 0000000000000000000000000000000000000000..24042d7100f9c2b3f0618d37a60bc2356ac27a1f
--- /dev/null
+++ b/projects/nshmp-apps/cypress/utils/gmm-lib.utils.ts
@@ -0,0 +1,92 @@
+export function hasGmmMenu() {
+  it('Has GMM select menu', () => {
+    cy.get('nshmp-template-control-panel')
+      .find('gmm-lib-gmm-menu')
+      .scrollIntoView()
+      .should('be.visible')
+      .within(() => {
+        cy.get('.sort-buttons')
+          .find('.gmm-group-toggle')
+          .should('have.attr', 'value', 'gmmGroup')
+          .should('have.class', 'mat-button-toggle-checked');
+
+        cy.get('select').find('optgroup').should('be.visible').should('have.length.above', 1);
+
+        cy.get('.sort-buttons')
+          .find('.gmm-alpha-toggle')
+          .should('have.attr', 'value', 'gmmAlpha')
+          .find('button')
+          .click()
+          .parent()
+          .should('have.class', 'mat-button-toggle-checked');
+
+        cy.get('select').find('optgroup').should('not.exist');
+
+        cy.get('select').find('option').should('have.length.above', 1);
+
+        cy.get('select').scrollTo('bottom').should('be.visible');
+      });
+  });
+}
+
+export function setMagnitude() {
+  cy.get('nshmp-template-control-panel')
+    .find('.mw-input')
+    .scrollIntoView()
+    .find('input')
+    .should('be.visible')
+    .clear()
+    .type('6.0')
+    .should('have.value', '6.0');
+}
+
+export function setSiteParameters() {
+  cy.get('nshmp-template-control-panel').within(() => {
+    cy.get('.vs30-input')
+      .scrollIntoView()
+      .find('input')
+      .should('be.visible')
+      .clear()
+      .type('760')
+      .should('have.value', '760');
+
+    cy.get('.z1p0-input')
+      .scrollIntoView()
+      .find('input')
+      .should('be.visible')
+      .should('have.value', '');
+
+    cy.get('.z2p5-input')
+      .scrollIntoView()
+      .find('input')
+      .should('be.visible')
+      .should('have.value', '');
+  });
+}
+export function setSourceGeometryParameters() {
+  cy.get('nshmp-template-control-panel').within(() => {
+    cy.get('.ztop-input')
+      .scrollIntoView()
+      .find('input')
+      .should('be.visible')
+      .clear()
+      .type('1.0')
+      .should('have.value', '1.0');
+
+    cy.get('.dip-input')
+      .scrollIntoView()
+      .find('input')
+      .should('be.visible')
+      .clear()
+      .type('90')
+      .should('have.value', '90');
+
+    cy.get('.width-input')
+      .scrollIntoView()
+      .find('input')
+      .should('be.visible')
+      .clear()
+      .type('14')
+      .should('have.value', '14');
+  });
+}
diff --git a/projects/nshmp-apps/cypress/utils/hazard-lib.utils.ts b/projects/nshmp-apps/cypress/utils/hazard-lib.utils.ts
new file mode 100644
index 0000000000000000000000000000000000000000..10e6fa00df6c98b7c4aa733bd5ebaee913cb1d96
--- /dev/null
+++ b/projects/nshmp-apps/cypress/utils/hazard-lib.utils.ts
@@ -0,0 +1,48 @@
+import * as utils from './utils';
+
+export function hasHazardModelMenu() {
+  it('Has model menu', () => {
+    cy.get('nshmp-template-control-panel')
+      .find('hazard-lib-model-form')
+      .scrollIntoView()
+      .find('mat-select')
+      .should('be.visible');
+  });
+}
+
+export function setHazardCommonControlPanel() {
+  utils.setInput('.latitude-input', '34');
+  utils.setInput('.longitude-input', '-118');
+  cy.get('nshmp-template-control-panel')
+    .find('hazard-lib-site-class-form')
+    .scrollIntoView()
+    .find('mat-select')
+    .click();
+  utils.selectMatOption('BC');
+  utils.setInput('hazard-lib-return-period-form', '2475');
+}
+
+export function setHazardMaxDirection() {
+  cy.get('nshmp-template-control-panel')
+    .find('hazard-lib-max-direction-form')
+    .scrollIntoView()
+    .find('mat-checkbox')
+    .should('not.have.class', 'mat-checkbox-checked')
+    .should('be.visible')
+    .find('label')
+    .click()
+    .parent()
+    .should('have.class', 'mat-checkbox-checked');
+}
+export function setHazardTruncate() {
+  cy.get('nshmp-template-control-panel')
+    .find('hazard-lib-truncation-form')
+    .scrollIntoView()
+    .find('mat-checkbox')
+    .should('not.have.class', 'mat-checkbox-checked')
+    .should('be.visible')
+    .find('label')
+    .click()
+    .parent()
+    .should('have.class', 'mat-checkbox-checked');
+}
diff --git a/projects/nshmp-apps/cypress/utils/index.ts b/projects/nshmp-apps/cypress/utils/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c556eb19ebb4a9d01c26985bb03b5dbec3b2667f
--- /dev/null
+++ b/projects/nshmp-apps/cypress/utils/index.ts
@@ -0,0 +1,7 @@
+export * from './dashboard.utils';
+export * from './gmm-lib.utils';
+export * from './hazard-lib.utils';
+export * from './nshmp-lib.utils';
+export * from './nshmp-template.utils';
+export * from './plot-lib.utils';
+export * from './utils';
diff --git a/projects/nshmp-apps/cypress/utils/nshmp-lib.utils.ts b/projects/nshmp-apps/cypress/utils/nshmp-lib.utils.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3c3e93278a9a837d04a64c2ced870607b4f87a59
--- /dev/null
+++ b/projects/nshmp-apps/cypress/utils/nshmp-lib.utils.ts
@@ -0,0 +1,77 @@
+import {hasDownloadedFile} from './utils';
+
+export interface ControlPanelButtons {
+  hasSubmitButton: boolean;
+  hasServiceCallButton: boolean;
+  hasResetButton: boolean;
+}
+
+export function controlPanelButtonsEnabled(buttons: ControlPanelButtons) {
+  cy.get('nshmp-lib-control-panel-buttons').find('.form-buttons').as('buttons');
+
+  if (buttons.hasResetButton) {
+    cy.get('@buttons')
+      .find('.reset-button')
+      .should('be.visible')
+      .find('button')
+      .should('be.enabled');
+  }
+
+  if (buttons.hasServiceCallButton) {
+    cy.get('@buttons')
+      .find('.service-button')
+      .should('be.visible')
+      .find('button')
+      .should('be.enabled')
+      .click();
+
+    hasServiceCallPopUp();
+    cy.get('body').click();
+  }
+
+  if (buttons.hasSubmitButton) {
+    cy.get('@buttons')
+      .find('.submit-button')
+      .should('be.visible')
+      .find('button')
+      .should('be.enabled');
+  }
+}
+
+export function controlPanelButtonsNotEnabled(buttons: ControlPanelButtons) {
+  cy.get('nshmp-lib-control-panel-buttons').find('.form-buttons').as('buttons');
+
+  if (buttons.hasResetButton) {
+    cy.get('@buttons').find('.reset-button').should('be.visible').should('not.be.enabled');
+  }
+
+  if (buttons.hasServiceCallButton) {
+    cy.get('@buttons').find('.service-button').should('be.visible').should('not.be.enabled');
+  }
+
+  if (buttons.hasSubmitButton) {
+    cy.get('@buttons').find('.submit-button').should('be.visible').should('not.be.enabled');
+  }
+}
+
+export function exportDataTableNotExist() {
+  cy.get('nshmp-lib-export-data-table').should('not.exist');
+}
+
+export function dataTableNotExist() {
+  cy.get('nshmp-lib-data-table').should('not.exist');
+}
+
+export function hasExportDataTable(filename: string) {
+  cy.get('nshmp-lib-export-data-table').find('button').should('be.enabled').click();
+
+  hasDownloadedFile(filename);
+}
+
+export function hasDataTable() {
+  cy.get('nshmp-lib-data-table').find('table').find('td').should('have.length.greaterThan', 1);
+}
+
+export function hasServiceCallPopUp() {
+  cy.get('nshmp-lib-url-bottom-sheet').should('be.visible');
+}
diff --git a/projects/nshmp-apps/cypress/utils/nshmp-template.utils.ts b/projects/nshmp-apps/cypress/utils/nshmp-template.utils.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6db8a621785f4c7c6084b1f61f3eab522f9ad79a
--- /dev/null
+++ b/projects/nshmp-apps/cypress/utils/nshmp-template.utils.ts
@@ -0,0 +1,227 @@
+import {NavigationList} from '@ghsc/nshmp-template';
+
+export interface CheckPlot {
+  plotClass: string;
+  intercept?: boolean;
+}
+
+export interface CheckPlots {
+  firstPlotClass: string;
+  secondPlotClass: string;
+
+  intercept?: boolean;
+}
+
+export function canResetControlPanel(setControlPanel: Function) {
+  setControlPanel();
+
+  cy.get('nshmp-lib-control-panel-buttons').find('.reset-button').find('button').click();
+}
+
+export function clickSettingsPanel() {
+  cy.get('nshmp-template-app-controls').find('.settings-toggle').find('button').click();
+}
+
+export function hasControlPanel() {
+  it('Has control panel', () => {
+    cy.get('nshmp-template-control-panel').should('be.visible');
+  });
+}
+
+export function hasNshmpTemplate(navList: NavigationList[]) {
+  it('Has nshmp-template', () => {
+    cy.get('nshmp-template').should('be.visible');
+  });
+
+  it('Has nshmp-template header', () => {
+    cy.get('nshmp-template-header').should('be.visible');
+  });
+
+  it('Has nshmp-template header logo', () => {
+    cy.get('div.nshmp-template-header--logo')
+      .should('be.visible')
+      .find('img')
+      .should('be.visible')
+      .should('have.attr', 'src', 'assets/usgs-logo.svg');
+  });
+
+  it('Return to dashboard when logo clicked', () => {
+    cy.get('div.nshmp-template-header--logo')
+      .should('be.visible')
+      .find('a')
+      .should('be.visible')
+      .click()
+      .url()
+      .should('eq', Cypress.config().baseUrl);
+  });
+
+  describe('Navigation Dropdown', () => {
+    it('Has navigation dropdown', () => {
+      cy.get('nshmp-template-navigation')
+        .find('button')
+        .click()
+        .get('.mat-menu-panel')
+        .should('be.visible')
+        .find('button')
+        .should('have.length.above', 0);
+    });
+
+    navList.forEach(nav => {
+      if (nav.subHeader) {
+        it(`Has "${nav.subHeader}" header`, () => {
+          cy.get('nshmp-template-navigation')
+            .find('button')
+            .click()
+            .get('.mat-menu-panel')
+            .contains(nav.subHeader);
+        });
+      }
+      nav.navigation.forEach(app => {
+        const routerLink = app.routerLink.startsWith('/')
+          ? app.routerLink.substring(1)
+          : app.routerLink;
+
+        it(`Has "${app.display}" listed in navigation menu`, () => {
+          if (app.routerLink.includes('/aws/')) {
+            cy.intercept('GET', 'https://earthquake.usgs.gov/ws/nshmp/hazard-runs/auth', {
+              fixture: 'aws-auth.json',
+            });
+          }
+
+          cy.get('nshmp-template-navigation').find('button').click();
+
+          cy.get('.mat-menu-panel')
+            .then(menu => {
+              const subMenu = menu.find(`button:contains('${nav.subHeader}')`);
+              if (subMenu.length) {
+                cy.get('.mat-menu-panel')
+                  .find(`button:contains('${nav.subHeader}')`)
+                  .trigger('mouseenter');
+              }
+            })
+            .get('.mat-menu-panel')
+            .contains('button', app.display)
+            .click()
+            .url()
+            .should('contain', `${Cypress.config().baseUrl}${routerLink}`);
+        });
+      });
+    });
+  });
+}
+
+export function hasPlotContent() {
+  it('Has plot content', () => {
+    cy.get('nshmp-template-plot-content').should('be.visible');
+  });
+}
+
+export function submitFormCheckPlot(options: CheckPlot) {
+  options.intercept = true ? options.intercept === undefined : options.intercept;
+
+  cy.get('nshmp-template-plot-content')
+    .find(options.plotClass)
+    .then(originalPlot => {
+      if (options.intercept) {
+        cy.intercept('**').as('service-call');
+      }
+
+      cy.get('nshmp-lib-control-panel-buttons').find('.submit-button').find('button').click();
+
+      if (options.intercept) {
+        cy.wait('@service-call');
+      }
+
+      cy.get('nshmp-template-plot-content')
+        .find(options.plotClass)
+        .should(updatedPlot => {
+          expect(originalPlot).to.not.equal(updatedPlot);
+        });
+    });
+}
+
+export function submitFormCheckPlots(options: CheckPlots) {
+  options.intercept = true ? options.intercept === undefined : options.intercept;
+
+  cy.get('nshmp-template-plot-content')
+    .find(options.firstPlotClass)
+    .then(originalFirstPlot => {
+      cy.get('nshmp-template-plot-content')
+        .find(options.secondPlotClass)
+        .then(originalSecondPlot => {
+          if (options.intercept) {
+            cy.intercept('**').as('service-call');
+          }
+
+          cy.get('nshmp-lib-control-panel-buttons').find('.submit-button').find('button').click();
+
+          if (options.intercept) {
+            cy.wait('@service-call', {
+              timeout: 30000,
+            });
+          }
+
+          cy.get('nshmp-template-plot-content')
+            .find(options.firstPlotClass)
+            .should(updatedFirstPlot => {
+              expect(originalFirstPlot).to.not.equal(updatedFirstPlot);
+            });
+          cy.get('nshmp-template-plot-content')
+            .find(options.secondPlotClass)
+            .should(updatedSecondPlot => {
+              expect(originalSecondPlot).to.not.equal(updatedSecondPlot);
+            });
+        });
+    });
+}
+
+export function toggleControlPanel() {
+  it('Toggles control panel', () => {
+    cy.get('nshmp-template-app-controls')
+      .find('.control-panel-toggle')
+      .should('have.class', 'mat-button-toggle-checked')
+      .find('button')
+      .should('be.enabled')
+      .as('control-panel-button');
+
+    cy.get('nshmp-template-control-panel').should('be.visible');
+
+    cy.get('@control-panel-button').click();
+
+    cy.get('nshmp-template-control-panel').should('not.exist');
+  });
+}
+
+export function togglePlotPanel() {
+  it('Toggles plot content', () => {
+    cy.get('nshmp-template-app-controls')
+      .find('.plot-toggle')
+      .should('have.class', 'mat-button-toggle-checked')
+      .find('button')
+      .should('be.enabled')
+      .as('plot-button');
+
+    cy.get('nshmp-template-plot-content').should('be.visible');
+
+    cy.get('@plot-button').click();
+
+    cy.get('nshmp-template-plot-content').should('not.exist');
+  });
+}
+
+export function toggleSettingPanel() {
+  it('Toggles setting panel', () => {
+    cy.get('nshmp-template-app-controls')
+      .find('.settings-toggle')
+      .should('not.have.class', 'mat-button-toggle-checked')
+      .find('button')
+      .should('be.enabled')
+      .as('settings-button');
+
+    cy.get('nshmp-template-settings').should('not.exist');
+
+    cy.get('@settings-button').click();
+
+    cy.get('nshmp-template-settings').find('form').should('be.visible');
+  });
+}
diff --git a/projects/nshmp-apps/cypress/utils/plot-lib.utils.ts b/projects/nshmp-apps/cypress/utils/plot-lib.utils.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cb0dcf9ac3a318a76f4e6013d3c6478205fa4b1b
--- /dev/null
+++ b/projects/nshmp-apps/cypress/utils/plot-lib.utils.ts
@@ -0,0 +1,154 @@
+import * as utils from './nshmp-template.utils';
+
+const testText = 'Test Text';
+
+type Axis = 'x' | 'y';
+
+export function changePlotSettings(plotClass: string, settingsClass: string) {
+  describe(`Change plot settings [plot ${plotClass}, settings ${settingsClass}]`, () => {
+    beforeEach(() => {
+      utils.clickSettingsPanel();
+      cy.get('nshmp-template-settings').find(settingsClass).scrollIntoView().as('plot-settings');
+      cy.get('nshmp-template-plot-content').find(plotClass).scrollIntoView().as('plot');
+    });
+
+    it('Plot title change', () => {
+      cy.get('@plot-settings')
+        .find('.title-input')
+        .scrollIntoView()
+        .should('be.visible')
+        .find('input')
+        .clear()
+        .type(testText)
+        .should('have.value', testText);
+
+      cy.get('@plot').contains('text', testText).should('be.visible');
+
+      cy.get('@plot-settings')
+        .find('.title-font-size-input')
+        .scrollIntoView()
+        .should('be.visible')
+        .find('input')
+        .clear()
+        .type('20')
+        .should('have.value', '20');
+
+      cy.get('@plot').contains('text', testText).should('have.css', 'font-size', '20px');
+    });
+
+    it('Plot height change', () => {
+      cy.get('@plot-settings')
+        .find('.plot-height-input')
+        .scrollIntoView()
+        .should('be.visible')
+        .find('input')
+        .clear()
+        .type('800')
+        .should('have.value', '800');
+
+      cy.get('@plot').find('svg').should('have.attr', 'height', '800');
+    });
+
+    it('X-Axis title change', () => {
+      axisTitleChange('x');
+    });
+
+    it('X-Axis axis change', () => {
+      axisChange('x');
+    });
+
+    it('Y-Axis title change', () => {
+      axisTitleChange('y');
+    });
+
+    it('Y-Axis axis change', () => {
+      axisChange('y');
+    });
+
+    it('Reset plot settings', () => {
+      cy.get('nshmp-template-settings')
+        .find('.form-buttons')
+        .contains('button', 'Reset')
+        .as('reset-button')
+        .should('be.visible')
+        .should('not.be.enabled');
+
+      cy.get('@plot-settings')
+        .find('.title-input')
+        .find('input')
+        .then(originalInput => {
+          cy.get('@plot-settings')
+            .find('.title-input')
+            .find('input')
+            .clear()
+            .type(testText)
+            .should('have.value', testText);
+
+          cy.get('@reset-button').should('be.enabled').click();
+
+          cy.get('@plot-settings')
+            .find('.title-input')
+            .find('input')
+            .should(resetInput => {
+              expect(originalInput.text()).to.equal(resetInput.text());
+            });
+        });
+    });
+  });
+}
+
+function axisChange(axis: Axis) {
+  const axisClass = axis === 'x' ? '.x-axis-settings' : '.y-axis-settings';
+  const axisLayerClass = axis === 'x' ? '.xaxislayer-above' : '.yaxislayer-above';
+
+  cy.get('@plot-settings')
+    .find(axisClass)
+    .find('mat-button-toggle-group')
+    .scrollIntoView()
+    .as('axis-toggles')
+    .contains('button', 'Log')
+    .should('be.visible')
+    .click()
+    .get('@plot')
+    .find(axisLayerClass)
+    .then(logTicks => {
+      cy.get('@axis-toggles')
+        .contains('button', 'Linear')
+        .scrollIntoView()
+        .should('be.visible')
+        .click()
+        .get('@plot')
+        .find(axisLayerClass)
+        .should(linearTicks => {
+          expect(logTicks).to.not.equal(linearTicks);
+        });
+    });
+}
+
+function axisTitleChange(axis: Axis) {
+  const axisClass = axis === 'x' ? '.x-axis-settings' : '.y-axis-settings';
+
+  cy.get('@plot-settings')
+    .find(axisClass)
+    .scrollIntoView()
+    .should('be.visible')
+    .find('.axis-title-input')
+    .find('input')
+    .clear()
+    .type(testText)
+    .should('have.value', testText);
+
+  cy.get('@plot').contains('text', testText).should('be.visible');
+
+  cy.get('@plot-settings')
+    .find(axisClass)
+    .scrollIntoView()
+    .should('be.visible')
+    .find('.axis-title-font-size-input')
+    .find('input')
+    .clear()
+    .type('20')
+    .should('have.value', '20');
+
+  cy.get('@plot').contains('text', testText).should('have.css', 'font-size', '20px');
+}
diff --git a/projects/nshmp-apps/cypress/utils/utils.ts b/projects/nshmp-apps/cypress/utils/utils.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c72f495e14933daa097ddcd40d8c7c65dcd6dd15
--- /dev/null
+++ b/projects/nshmp-apps/cypress/utils/utils.ts
@@ -0,0 +1,34 @@
+export function hasApplicationTab(tabClass: string) {
+  cy.get('nshmp-template-plot-content')
+    .find('mat-tab-group')
+    .find(tabClass)
+    .should('be.visible')
+    .click();
+}
+
+export function hasDownloadedFile(filename: string) {
+  const downloadsFolder = Cypress.config('downloadsFolder');
+  const file = `${downloadsFolder}/${filename}`;
+
+  cy.readFile(file).should('exist').should('have.length.above', 1);
+}
+
+export function setInput(inputClass: string, value: string) {
+  cy.get('nshmp-template-control-panel')
+    .find(inputClass)
+    .find('input')
+    .scrollIntoView()
+    .should('be.visible')
+    .clear()
+    .type(value)
+    .should('have.value', value);
+}
+export function selectMatOption(value: string) {
+  cy.get('.cdk-overlay-container')
+    .find('mat-option')
+    .each(option => {
+      if (option.attr('ng-reflect-value') === value) {
+        cy.wrap(option).click();
+      }
+    });
+}
diff --git a/projects/nshmp-apps/src/app/dashboard/app.component.ts b/projects/nshmp-apps/src/app/dashboard/app.component.ts
index e353e32c9bd99b38f1372d7d214e7ff3f1cf5f0b..2a7b8d2a309ed3537531ce83f034a92e9fc393c2 100644
--- a/projects/nshmp-apps/src/app/dashboard/app.component.ts
+++ b/projects/nshmp-apps/src/app/dashboard/app.component.ts
@@ -7,9 +7,7 @@ import * as nav from 'projects/nshmp-lib/src/lib/utils/navigation.utils';
   styleUrls: ['./app.component.scss'],
 })
 export class AppComponent {
-  navigationList = nav
-    .navigation()
-    .filter(navList => navList.navigation.find(nav => nav.display !== 'Dashboard'));
+  navigationList = nav.navigation();
 
   gmmApps = nav.gmmApps();
   mainApps = nav.mainApps();
diff --git a/projects/nshmp-apps/src/app/dev/dashboard/app.component.ts b/projects/nshmp-apps/src/app/dev/dashboard/app.component.ts
index 6ce6b5e20d6e88f4c990686a62e28433fea00d50..8805cd1bc072391ed1cc1f6a84eca71508b64d23 100644
--- a/projects/nshmp-apps/src/app/dev/dashboard/app.component.ts
+++ b/projects/nshmp-apps/src/app/dev/dashboard/app.component.ts
@@ -8,9 +8,7 @@ import * as nav from 'projects/nshmp-lib/src/lib/utils/navigation.utils';
   styleUrls: ['./app.component.scss'],
 })
 export class AppComponent implements OnInit {
-  navigationList = nav
-    .devNavigation()
-    .filter(navList => navList.navigation.find(nav => nav.display !== 'Development Dashboard'));
+  navigationList = nav.devNavigation();
 
   awsApps = nav.devAwsApps();
   mainApps = nav.devMainApps();
diff --git a/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/components/control-panel/control-panel.component.html b/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/components/control-panel/control-panel.component.html
index 1b7ae95c9db2e34a86353f9a342e73cb67e28671..5b8f6609d973012ca13c13c361303675f104fc2c 100644
--- a/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/components/control-panel/control-panel.component.html
+++ b/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/components/control-panel/control-panel.component.html
@@ -6,7 +6,7 @@
   (submit)="exceedanceFacade.calculateExceedance()"
 >
   <!-- Median -->
-  <mat-form-field class="grid-col-12">
+  <mat-form-field class="grid-col-12 median-input">
     <mat-label> Median (g) </mat-label>
     <input
       type="number"
@@ -20,7 +20,7 @@
   </mat-form-field>
 
   <!-- Sigma -->
-  <mat-form-field class="grid-col-12">
+  <mat-form-field class="grid-col-12 sigma-input">
     <mat-label> Sigma (natural log units) </mat-label>
     <input
       type="number"
@@ -34,7 +34,7 @@
   </mat-form-field>
 
   <!-- Rate -->
-  <mat-form-field class="grid-col-12">
+  <mat-form-field class="grid-col-12 rate-input">
     <mat-label> Rate </mat-label>
     <input
       type="number"
@@ -48,12 +48,16 @@
   </mat-form-field>
 
   <!-- Truncation -->
-  <mat-checkbox color="primary" class="margin-left-1" [ngrxFormControlState]="truncation$ | async">
+  <mat-checkbox
+    color="primary"
+    class="margin-left-1 truncation-checkbox"
+    [ngrxFormControlState]="truncation$ | async"
+  >
     Truncation
   </mat-checkbox>
 
   <!-- Truncation Level -->
-  <mat-form-field class="grid-col-12">
+  <mat-form-field class="grid-col-12 truncation-level-input">
     <mat-label> Truncation Level (n) </mat-label>
     <input
       [disabled]="(truncation$ | async)?.value === false"
diff --git a/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/components/plot/plot.component.html b/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/components/plot/plot.component.html
index f9990b49f5c0e51f10a472df3d17b80222bf5509..2f46fba108d0dbd882eb4d7a093e2660b3b1e0ef 100644
--- a/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/components/plot/plot.component.html
+++ b/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/components/plot/plot.component.html
@@ -2,6 +2,7 @@
 <div class="height-full overflow-auto">
   <div class="grid-container nshmp-plotly-plot">
     <plotly-plot
+      class="exceedance-plot"
       [divId]="(plotData$ | async)?.id"
       [config]="(plotData$ | async)?.config"
       [data]="(plotData$ | async)?.data"
diff --git a/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/components/settings/settings.component.html b/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/components/settings/settings.component.html
index eb4ffb7ae5631f390ca1e46e8bb4d883d4182488..320a5fc435e88b23ecd1c96edc5da0e5abd09975 100644
--- a/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/components/settings/settings.component.html
+++ b/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/components/settings/settings.component.html
@@ -1,5 +1,6 @@
 <!-- Exceedance plot settings -->
-<plot-lib-plot-settings *ngIf="plot$ | async" [plot]="plot$ | async"> </plot-lib-plot-settings>
+<plot-lib-plot-settings class="exceedance-settings" *ngIf="plot$ | async" [plot]="plot$ | async">
+</plot-lib-plot-settings>
 
 <div class="padding-y-3"></div>
 
diff --git a/projects/nshmp-apps/src/app/gmm/distance/components/content/content.component.html b/projects/nshmp-apps/src/app/gmm/distance/components/content/content.component.html
index 75c68e3fd3754c29247a6453b5a678d4fc0c131d..f39932be3572aace0525cde7d6fb0b193e2892c6 100644
--- a/projects/nshmp-apps/src/app/gmm/distance/components/content/content.component.html
+++ b/projects/nshmp-apps/src/app/gmm/distance/components/content/content.component.html
@@ -1,11 +1,11 @@
 <div class="height-full overflow-auto">
   <mat-tab-group dynamicHeight (selectedTabChange)="gmmDistanceFacade.plotRedraw()">
-    <mat-tab label="Plots">
+    <mat-tab labelClass="plots-tab" label="Plots">
       <div>
         <app-plots></app-plots>
       </div>
     </mat-tab>
-    <mat-tab label="Medians">
+    <mat-tab labelClass="medians-tab" label="Medians">
       <div>
         <nshmp-lib-export-data-table
           [table]="table$ | async"
diff --git a/projects/nshmp-apps/src/app/gmm/distance/components/control-panel/control-panel.component.html b/projects/nshmp-apps/src/app/gmm/distance/components/control-panel/control-panel.component.html
index 5313dd66c68e684882f0b56f2927c2863a60af6b..289233f8376195fcb0cfcd58b3e8a7ce82546b82 100644
--- a/projects/nshmp-apps/src/app/gmm/distance/components/control-panel/control-panel.component.html
+++ b/projects/nshmp-apps/src/app/gmm/distance/components/control-panel/control-panel.component.html
@@ -5,7 +5,7 @@
     [ngrxFormState]="form$ | async"
     (submit)="gmmDistanceFacade.callService()"
   >
-    <mat-form-field class="grid-col-12">
+    <mat-form-field class="grid-col-12 multi-select-parameter-menu">
       <mat-label>Multi-Selectable Parameter</mat-label>
       <mat-select [ngrxFormControlState]="(controls$ | async)?.multiSelectableParam">
         <mat-option value="gmm">Ground Motion Models</mat-option>
@@ -25,7 +25,7 @@
     </gmm-lib-gmm-menu>
 
     <!-- IMT select menu -->
-    <mat-form-field class="grid-col-12">
+    <mat-form-field class="grid-col-12 imt-select">
       <mat-label>Intensity Measure Type</mat-label>
       <mat-select #imtSelectEl [ngrxFormControlState]="(controls$ | async)?.imt">
         <mat-option *ngIf="(gmmSelected$ | async) === false" selected="true" value="default">
@@ -38,13 +38,13 @@
     </mat-form-field>
 
     <!-- Event parameters -->
-    <div class="settings-subsection">
+    <div class="settings-subsection event-parameters">
       <mat-label class="settings-subsection--label">Event Parameters</mat-label>
 
       <!-- Event parameters: Mw input -->
       <div class="settings-subsection--section">
         <gmm-lib-gmm-multi-select
-          class="grid-col-6"
+          class="grid-col-6 mw-input"
           label="Mw"
           [multiple]="MwMultiple$ | async"
           [numberControl]="(controls$ | async)?.Mw"
@@ -58,12 +58,12 @@
     </div>
 
     <!-- Source geometry -->
-    <div class="settings-subsection">
+    <div class="settings-subsection geometry-parameters">
       <mat-label class="settings-subsection--label">Source Geometry</mat-label>
 
       <div class="settings-subsection--section grid-row grid-gap-sm">
         <!-- Source geometry: zTop input -->
-        <mat-form-field class="grid-col-4">
+        <mat-form-field class="grid-col-4 ztop-input">
           <mat-label>Z<sub>TOP</sub> (km)</mat-label>
           <input
             matInput
@@ -82,7 +82,7 @@
         </mat-form-field>
 
         <!-- Source geometry: Dip input -->
-        <mat-form-field class="grid-col-4">
+        <mat-form-field class="grid-col-4 dip-input">
           <mat-label>Dip (°)</mat-label>
           <input
             matInput
@@ -101,7 +101,7 @@
         </mat-form-field>
 
         <!-- Source geometry: Width input -->
-        <mat-form-field class="grid-col-4">
+        <mat-form-field class="grid-col-4 width-input">
           <mat-label>Width (km)</mat-label>
           <input
             matInput
@@ -122,13 +122,13 @@
     </div>
 
     <!-- Site & Basin-->
-    <div class="settings-subsection">
+    <div class="settings-subsection site-parameters">
       <mat-label class="settings-subsection--label">Site & Basin</mat-label>
 
       <div class="settings-subsection--section grid-row grid-gap-sm">
         <!-- Site & Basin: Vs30 input -->
         <gmm-lib-gmm-multi-select
-          class="grid-col-6"
+          class="grid-col-6 vs30-input"
           [label]="vs30Label"
           [multiple]="vs30Multiple$ | async"
           [numberControl]="(controls$ | async)?.vs30"
@@ -140,7 +140,7 @@
         </gmm-lib-gmm-multi-select>
 
         <!-- Site & Basin: Z 1.0 input -->
-        <mat-form-field class="grid-col-3">
+        <mat-form-field class="grid-col-3 z1p0-input">
           <mat-label>Z<sub>1.0</sub> (km)</mat-label>
           <input
             matInput
@@ -159,7 +159,7 @@
         </mat-form-field>
 
         <!-- Site & Basin: Z 2.5 input -->
-        <mat-form-field class="grid-col-3">
+        <mat-form-field class="grid-col-3 z2p5-input">
           <mat-label>Z<sub>2.5</sub> (km)</mat-label>
           <input
             matInput
diff --git a/projects/nshmp-apps/src/app/gmm/distance/components/plots/plots.component.html b/projects/nshmp-apps/src/app/gmm/distance/components/plots/plots.component.html
index 13ac3390971c1a1c2442b726b250a7849c811d4b..504dfb3bbc388d16a0925fe0905601dc10c72723 100644
--- a/projects/nshmp-apps/src/app/gmm/distance/components/plots/plots.component.html
+++ b/projects/nshmp-apps/src/app/gmm/distance/components/plots/plots.component.html
@@ -1,6 +1,7 @@
 <!-- Means -->
 <div *ngIf="meanPlotData$ | async" class="nshmp-plotly-plot">
   <plotly-plot
+    class="distance-plot"
     [config]="(meanPlotData$ | async)?.config"
     [className]="(meanPlotData$ | async)?.config?.className"
     [data]="(meanPlotData$ | async)?.data"
diff --git a/projects/nshmp-apps/src/app/gmm/distance/components/settings/settings.component.html b/projects/nshmp-apps/src/app/gmm/distance/components/settings/settings.component.html
index fa8cdb6bfd010bbe01fbf8fee0aaa59852ce4e2c..2323a712515eb5f066e0ddcd89626cde50d24de8 100644
--- a/projects/nshmp-apps/src/app/gmm/distance/components/settings/settings.component.html
+++ b/projects/nshmp-apps/src/app/gmm/distance/components/settings/settings.component.html
@@ -1,15 +1,23 @@
-<plot-lib-plot-settings *ngIf="meanPlot$ | async" [plot]="meanPlot$ | async">
-</plot-lib-plot-settings>
+<div class="height-full overflow-auto">
+  <plot-lib-plot-settings
+    *ngIf="meanPlot$ | async"
+    class="distance-settings"
+    [plot]="meanPlot$ | async"
+  >
+  </plot-lib-plot-settings>
 
-<div class="padding-y-3"></div>
+  <div class="padding-y-3"></div>
 
-<div class="form-buttons form-buttons--right">
-  <button
-    color="warn"
-    (click)="gmmDistanceFacade.resetPlotSettings()"
-    [disabled]="(meanPlotSettings$ | async)?.isPristine && (meanPlotSettings$ | async)?.isUntouched"
-    mat-raised-button
-  >
-    Reset
-  </button>
+  <div class="form-buttons form-buttons--right">
+    <button
+      color="warn"
+      (click)="gmmDistanceFacade.resetPlotSettings()"
+      [disabled]="
+        (meanPlotSettings$ | async)?.isPristine && (meanPlotSettings$ | async)?.isUntouched
+      "
+      mat-raised-button
+    >
+      Reset
+    </button>
+  </div>
 </div>
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/content/content.component.html b/projects/nshmp-apps/src/app/gmm/magnitude/components/content/content.component.html
index 251d6b4652bc5023072f733e35bdd8434244fe28..5d1cd287b1acfa9ddc0466969068bc2968b23a1f 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/content/content.component.html
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/content/content.component.html
@@ -1,10 +1,10 @@
 <mat-tab-group class="height-full" (selectedTabChange)="facade.plotRedraw()">
-  <mat-tab label="Plots">
+  <mat-tab labelClass="plots-tab" label="Plots">
     <div>
       <app-plots></app-plots>
     </div>
   </mat-tab>
-  <mat-tab label="Medians">
+  <mat-tab labelClass="medians-tab" label="Medians">
     <div>
       <nshmp-lib-export-data-table
         [table]="meanTable$ | async"
@@ -15,7 +15,7 @@
       <nshmp-lib-data-table [table]="meanTable$ | async"> </nshmp-lib-data-table>
     </div>
   </mat-tab>
-  <mat-tab label="Sigmas">
+  <mat-tab labelClass="sigmas-tab" label="Sigmas">
     <div>
       <nshmp-lib-export-data-table
         [table]="sigmaTable$ | async"
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/control-panel/control-panel.component.html b/projects/nshmp-apps/src/app/gmm/magnitude/components/control-panel/control-panel.component.html
index a5417e5ab9f6ecee4b8e4a84a926dc5fc4854855..4d1fd5ceb05a25a60ecc47034229282a0601d422 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/control-panel/control-panel.component.html
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/control-panel/control-panel.component.html
@@ -6,7 +6,7 @@
     (submit)="facade.callService()"
     novalidate
   >
-    <mat-form-field class="grid-col-12">
+    <mat-form-field class="grid-col-12 multi-select-parameter-menu">
       <mat-label>Multi-Selectable Parameter</mat-label>
       <mat-select [ngrxFormControlState]="(controls$ | async)?.multiSelectableParam">
         <mat-option value="gmm">Ground Motion Models</mat-option>
@@ -25,7 +25,7 @@
     </gmm-lib-gmm-menu>
 
     <!-- IMT select menu -->
-    <mat-form-field class="grid-col-12">
+    <mat-form-field class="grid-col-12 imt-select">
       <mat-label>Intensity Measure Type</mat-label>
       <mat-select #imtSelectEl [ngrxFormControlState]="(controls$ | async)?.imt">
         <mat-option *ngIf="(gmmSelected$ | async) === false" selected="true" value="default">
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/event-parameters/event-parameters.component.html b/projects/nshmp-apps/src/app/gmm/magnitude/components/event-parameters/event-parameters.component.html
index 618d7bce3648162d18ba59b2ce2304f192b0946e..0dc701e7a46e4647c6c0e16d670549713448280d 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/event-parameters/event-parameters.component.html
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/event-parameters/event-parameters.component.html
@@ -5,7 +5,7 @@
 
     <div class="settings-subsection--section grid-row grid-gap-sm">
       <!-- Event parameters: mMin input -->
-      <mat-form-field class="grid-col-4">
+      <mat-form-field class="grid-col-4 mmin-input">
         <mat-label>Minimum Mw</mat-label>
         <input
           matInput
@@ -25,7 +25,7 @@
       </mat-form-field>
 
       <!-- Event parameters: mMax input -->
-      <mat-form-field class="grid-col-4">
+      <mat-form-field class="grid-col-4 mmax-input">
         <mat-label>Maximum Mw</mat-label>
         <input
           matInput
@@ -45,7 +45,7 @@
       </mat-form-field>
 
       <!-- Event parameters: Step input -->
-      <mat-form-field class="grid-col-4">
+      <mat-form-field class="grid-col-4 mstep-input">
         <mat-label>Mw Step</mat-label>
         <input
           matInput
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/path-parameters/path-parameters.component.html b/projects/nshmp-apps/src/app/gmm/magnitude/components/path-parameters/path-parameters.component.html
index 3bd363b1a46a8c38acde6710fb081df103815d77..6d65c74e39289fa4b2337d210b1a7962307d5672 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/path-parameters/path-parameters.component.html
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/path-parameters/path-parameters.component.html
@@ -4,7 +4,7 @@
     <mat-label class="settings-subsection--label">Path Parameters</mat-label>
 
     <!-- Event parameters: Distance input -->
-    <div class="settings-subsection--section">
+    <div class="settings-subsection--section distance-input">
       <mat-form-field class="grid-col-12">
         <mat-label>Distance (km)</mat-label>
         <input
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/plots-settings/plots-settings.component.html b/projects/nshmp-apps/src/app/gmm/magnitude/components/plots-settings/plots-settings.component.html
index f3675c7697e7455eb97fa1c00c4a99734e21138b..5fdd22ec18c276c7efe66f929c0ab388e08084d6 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/plots-settings/plots-settings.component.html
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/plots-settings/plots-settings.component.html
@@ -1,23 +1,33 @@
-<plot-lib-plot-settings *ngIf="meanPlot$ | async" [plot]="meanPlot$ | async">
-</plot-lib-plot-settings>
+<div class="height-full overflow-auto">
+  <plot-lib-plot-settings
+    *ngIf="meanPlot$ | async"
+    [plot]="meanPlot$ | async"
+    class="mean-settings"
+  >
+  </plot-lib-plot-settings>
 
-<plot-lib-plot-settings *ngIf="sigmaPlot$ | async" [plot]="sigmaPlot$ | async">
-</plot-lib-plot-settings>
+  <plot-lib-plot-settings
+    *ngIf="sigmaPlot$ | async"
+    [plot]="sigmaPlot$ | async"
+    class="sigma-settings"
+  >
+  </plot-lib-plot-settings>
 
-<div class="padding-y-2"></div>
+  <div class="padding-y-2"></div>
 
-<div class="form-buttons form-buttons--right">
-  <button
-    color="warn"
-    (click)="facade.resetPlotSettings()"
-    [disabled]="
-      (meanPlotSettings$ | async)?.isPristine &&
-      (meanPlotSettings$ | async)?.isUntouched &&
-      (sigmaPlotSettings$ | async)?.isPristine &&
-      (sigmaPlotSettings$ | async)?.isPristine
-    "
-    mat-raised-button
-  >
-    Reset
-  </button>
+  <div class="form-buttons form-buttons--right">
+    <button
+      color="warn"
+      (click)="facade.resetPlotSettings()"
+      [disabled]="
+        (meanPlotSettings$ | async)?.isPristine &&
+        (meanPlotSettings$ | async)?.isUntouched &&
+        (sigmaPlotSettings$ | async)?.isPristine &&
+        (sigmaPlotSettings$ | async)?.isPristine
+      "
+      mat-raised-button
+    >
+      Reset
+    </button>
+  </div>
 </div>
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/plots/plots.component.html b/projects/nshmp-apps/src/app/gmm/magnitude/components/plots/plots.component.html
index eea68bbc65a47224403772f2ec44e33476f6d8b8..3ddca65be7a9e133a0e5c8789a809aab2c076abe 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/plots/plots.component.html
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/plots/plots.component.html
@@ -1,6 +1,7 @@
 <!-- Mean Plot -->
 <div *ngIf="meanPlotData$ | async" class="nshmp-plotly-plot">
   <plotly-plot
+    class="mean-plot"
     [config]="(meanPlotData$ | async)?.config"
     [className]="(meanPlotData$ | async)?.config?.className"
     [data]="(meanPlotData$ | async)?.data"
@@ -21,6 +22,7 @@
 <!-- Sigma Plot -->
 <div *ngIf="sigmaPlotData$ | async" class="nshmp-plotly-plot">
   <plotly-plot
+    class="sigma-plot"
     [config]="(sigmaPlotData$ | async)?.config"
     [className]="(sigmaPlotData$ | async)?.config?.className"
     [data]="(sigmaPlotData$ | async)?.data"
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/site-parameters/site-parameters.component.html b/projects/nshmp-apps/src/app/gmm/magnitude/components/site-parameters/site-parameters.component.html
index 58c58196af4ac95aeab6f58109ad0962f97abe0e..c5de94e45ea60198d240efa6d24a6f61a9e56d02 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/site-parameters/site-parameters.component.html
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/site-parameters/site-parameters.component.html
@@ -6,7 +6,7 @@
     <div class="settings-subsection--section grid-row grid-gap-sm">
       <!-- Site & Basin: Vs30 input -->
       <gmm-lib-gmm-multi-select
-        class="grid-col-6"
+        class="grid-col-6 vs30-input"
         [label]="vs30Label"
         [multiple]="vs30Multiple$ | async"
         [numberControl]="(controls$ | async)?.vs30"
@@ -18,7 +18,7 @@
       </gmm-lib-gmm-multi-select>
 
       <!-- Site & Basin: Z 1.0 input -->
-      <mat-form-field class="grid-col-3">
+      <mat-form-field class="grid-col-3 z1p0-input">
         <mat-label>Z<sub>1.0</sub> (km)</mat-label>
         <input
           matInput
@@ -37,7 +37,7 @@
       </mat-form-field>
 
       <!-- Site & Basin: Z 2.5 input -->
-      <mat-form-field class="grid-col-3">
+      <mat-form-field class="grid-col-3 z2p5-input">
         <mat-label>Z<sub>2.5</sub> (km)</mat-label>
         <input
           matInput
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/source-parameters/source-parameters.component.html b/projects/nshmp-apps/src/app/gmm/magnitude/components/source-parameters/source-parameters.component.html
index ed2c98f2c9319ea9841408a2df195728499a1900..a9e730a339881f06d3d926655e911f58615ca83f 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/source-parameters/source-parameters.component.html
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/source-parameters/source-parameters.component.html
@@ -5,7 +5,7 @@
 
     <div class="settings-subsection--section grid-row grid-gap-sm">
       <!-- Source Parameters: zTop input -->
-      <mat-form-field class="grid-col-4">
+      <mat-form-field class="grid-col-4 ztop-input">
         <mat-label>Z<sub>TOP</sub> (km)</mat-label>
         <input
           matInput
@@ -24,7 +24,7 @@
       </mat-form-field>
 
       <!-- Source Parameters: Dip input -->
-      <mat-form-field class="grid-col-4">
+      <mat-form-field class="grid-col-4 dip-input">
         <mat-label>Dip (°)</mat-label>
         <input
           matInput
@@ -43,7 +43,7 @@
       </mat-form-field>
 
       <!-- Source Parameters: Width input -->
-      <mat-form-field class="grid-col-4">
+      <mat-form-field class="grid-col-4 width-input">
         <mat-label>Width (km)</mat-label>
         <input
           matInput
diff --git a/projects/nshmp-apps/src/app/gmm/spectra/components/content/content.component.html b/projects/nshmp-apps/src/app/gmm/spectra/components/content/content.component.html
index 0cae82f07541d54da2ed6bda29d3e31e4ef3f501..f71515a4782679aa9d16eb7bcb8d7162aa562e23 100644
--- a/projects/nshmp-apps/src/app/gmm/spectra/components/content/content.component.html
+++ b/projects/nshmp-apps/src/app/gmm/spectra/components/content/content.component.html
@@ -1,10 +1,10 @@
 <mat-tab-group class="height-full" (selectedTabChange)="facade.plotRedraw()">
-  <mat-tab label="Plots">
+  <mat-tab labelClass="plots-tab" label="Plots">
     <div>
       <app-plots></app-plots>
     </div>
   </mat-tab>
-  <mat-tab label="Medians">
+  <mat-tab labelClass="medians-tab" label="Medians">
     <div>
       <nshmp-lib-export-data-table
         [table]="spectraTable$ | async"
@@ -14,7 +14,7 @@
       <nshmp-lib-data-table [table]="spectraTable$ | async"></nshmp-lib-data-table>
     </div>
   </mat-tab>
-  <mat-tab label="Sigmas">
+  <mat-tab labelClass="sigmas-tab" label="Sigmas">
     <div>
       <nshmp-lib-export-data-table
         [table]="sigmaTable$ | async"
diff --git a/projects/nshmp-apps/src/app/gmm/spectra/components/control-panel/control-panel.component.html b/projects/nshmp-apps/src/app/gmm/spectra/components/control-panel/control-panel.component.html
index 9967cae7d0894e19f334a94452b02b9bbe7e59d1..91a1219f50d8a7f900f040584b0d6229c15c79e5 100644
--- a/projects/nshmp-apps/src/app/gmm/spectra/components/control-panel/control-panel.component.html
+++ b/projects/nshmp-apps/src/app/gmm/spectra/components/control-panel/control-panel.component.html
@@ -6,7 +6,7 @@
     (submit)="facade.callService()"
     novalidate
   >
-    <mat-form-field class="grid-col-12">
+    <mat-form-field class="grid-col-12 multi-select-parameter-menu">
       <mat-label>Multi-Selectable Parameter</mat-label>
       <mat-select [ngrxFormControlState]="(controls$ | async)?.multiSelectableParam">
         <mat-option value="gmm">Ground Motion Model</mat-option>
diff --git a/projects/nshmp-apps/src/app/gmm/spectra/components/event-parameters/event-parameters.component.html b/projects/nshmp-apps/src/app/gmm/spectra/components/event-parameters/event-parameters.component.html
index b0a679736e8cb91db64d931466d46bba5c412909..4976f99fd0d73d1a5ef73c44812ad14290a92498 100644
--- a/projects/nshmp-apps/src/app/gmm/spectra/components/event-parameters/event-parameters.component.html
+++ b/projects/nshmp-apps/src/app/gmm/spectra/components/event-parameters/event-parameters.component.html
@@ -7,7 +7,7 @@
       <div class="grid-row grid-gap-sm">
         <!-- Event parameters: Mw input -->
         <gmm-lib-gmm-multi-select
-          class="grid-col-6"
+          class="grid-col-6 mw-input"
           label="M<sub>W</sub>"
           [multiple]="MwMultiple$ | async"
           [numberControl]="(controls$ | async)?.Mw"
@@ -19,7 +19,7 @@
         </gmm-lib-gmm-multi-select>
 
         <!-- Event Parameters: Rake -->
-        <mat-form-field class="grid-col-6">
+        <mat-form-field class="grid-col-6 rake-input">
           <mat-label>Rake (°)</mat-label>
           <input
             matInput
@@ -41,7 +41,7 @@
 
       <!-- Event Parameters: Rake Buttons -->
       <mat-button-toggle-group
-        class="grid-col-12"
+        class="grid-col-12 rake-buttons"
         [ngrxFormControlState]="(controls$ | async)?.rakeButton"
         [value]="(controls$ | async)?.rake?.value"
       >
@@ -51,7 +51,7 @@
       </mat-button-toggle-group>
 
       <!-- Event Parameters: zHyp -->
-      <div class="grid-row grid-gap-lg">
+      <div class="grid-row grid-gap-lg zhyp-input">
         <mat-form-field class="grid-col-4">
           <mat-label>Z<sub>HYP</sub> (km)</mat-label>
           <input
@@ -74,7 +74,7 @@
         <!-- Event Parameters: centered down dip -->
         <div class="grid-col-8">
           <mat-checkbox
-            class=""
+            class="down-dip-checkbox"
             color="primary"
             [ngrxFormControlState]="(controls$ | async)?.zHypCentered"
           >
diff --git a/projects/nshmp-apps/src/app/gmm/spectra/components/path-parameters/path-parameters.component.html b/projects/nshmp-apps/src/app/gmm/spectra/components/path-parameters/path-parameters.component.html
index 54aeba5081aa3bf21007a2af8f167a0d9c17b49f..78bb63561374e43c6cb7a8494d1607552b956b21 100644
--- a/projects/nshmp-apps/src/app/gmm/spectra/components/path-parameters/path-parameters.component.html
+++ b/projects/nshmp-apps/src/app/gmm/spectra/components/path-parameters/path-parameters.component.html
@@ -6,7 +6,7 @@
     <div class="settings-subsection--section">
       <div class="grid-row grid-gap-sm">
         <!-- Path Parameters: rX input -->
-        <mat-form-field class="grid-col-4">
+        <mat-form-field class="grid-col-4 rx-input">
           <mat-label>R<sub>X</sub> (km)</mat-label>
           <input
             matInput
@@ -25,7 +25,7 @@
         </mat-form-field>
 
         <!-- Path Parameters: rRup input -->
-        <mat-form-field class="grid-col-4">
+        <mat-form-field class="grid-col-4 rrup-input">
           <mat-label>R<sub>RUP</sub> (km)</mat-label>
           <input
             matInput
@@ -45,7 +45,7 @@
         </mat-form-field>
 
         <!-- Path Parameters: rJB input -->
-        <mat-form-field class="grid-col-4">
+        <mat-form-field class="grid-col-4 rjb-input">
           <mat-label>R<sub>JB</sub> (km)</mat-label>
           <input
             matInput
@@ -68,7 +68,7 @@
       <!-- Path Parameters: Derive rJB and rRup -->
       <div class="grid-row">
         <mat-checkbox
-          class="grid-col-12"
+          class="grid-col-12 rjb-rrup-checkbox"
           color="primary"
           [ngrxFormControlState]="(controls$ | async)?.derivePathParams"
         >
@@ -78,7 +78,7 @@
 
       <!-- Path Parameters: Hanging wall or footwall -->
       <mat-button-toggle-group
-        class="grid-col-12"
+        class="grid-col-12 hanging-wall-button"
         [ngrxFormControlState]="(controls$ | async)?.hangingWall"
       >
         <mat-button-toggle class="grid-col-6" [value]="true"> Hanging Wall </mat-button-toggle>
diff --git a/projects/nshmp-apps/src/app/gmm/spectra/components/plots-settings/plots-settings.component.html b/projects/nshmp-apps/src/app/gmm/spectra/components/plots-settings/plots-settings.component.html
index e1b269cfb9402740965d144201b9bd39c1efb7c1..f96e46d1b37c1820b8dee836af14627faa14679d 100644
--- a/projects/nshmp-apps/src/app/gmm/spectra/components/plots-settings/plots-settings.component.html
+++ b/projects/nshmp-apps/src/app/gmm/spectra/components/plots-settings/plots-settings.component.html
@@ -1,23 +1,33 @@
-<plot-lib-plot-settings *ngIf="spectraPlot$ | async" [plot]="spectraPlot$ | async">
-</plot-lib-plot-settings>
+<div class="height-full overflow-auto">
+  <plot-lib-plot-settings
+    class="spectra-settings"
+    *ngIf="spectraPlot$ | async"
+    [plot]="spectraPlot$ | async"
+  >
+  </plot-lib-plot-settings>
 
-<plot-lib-plot-settings *ngIf="sigmaPlot$ | async" [plot]="sigmaPlot$ | async">
-</plot-lib-plot-settings>
+  <plot-lib-plot-settings
+    class="sigma-settings"
+    *ngIf="sigmaPlot$ | async"
+    [plot]="sigmaPlot$ | async"
+  >
+  </plot-lib-plot-settings>
 
-<div class="padding-y-3"></div>
+  <div class="padding-y-3"></div>
 
-<div class="form-buttons form-buttons--right">
-  <button
-    color="warn"
-    (click)="facade.resetPlotSettings()"
-    [disabled]="
-      (spectraPlotSettings$ | async)?.isPristine &&
-      (spectraPlotSettings$ | async)?.isUntouched &&
-      (sigmaPlotSettings$ | async)?.isPristine &&
-      (sigmaPlotSettings$ | async)?.isPristine
-    "
-    mat-raised-button
-  >
-    Reset
-  </button>
+  <div class="form-buttons form-buttons--right">
+    <button
+      color="warn"
+      (click)="facade.resetPlotSettings()"
+      [disabled]="
+        (spectraPlotSettings$ | async)?.isPristine &&
+        (spectraPlotSettings$ | async)?.isUntouched &&
+        (sigmaPlotSettings$ | async)?.isPristine &&
+        (sigmaPlotSettings$ | async)?.isPristine
+      "
+      mat-raised-button
+    >
+      Reset
+    </button>
+  </div>
 </div>
diff --git a/projects/nshmp-apps/src/app/gmm/spectra/components/plots/plots.component.html b/projects/nshmp-apps/src/app/gmm/spectra/components/plots/plots.component.html
index 2d011a93c25ea530b5f2bf0b49b893369788caa3..b9c69d11839eb6677ebbbb76cbc093b395a33d9b 100644
--- a/projects/nshmp-apps/src/app/gmm/spectra/components/plots/plots.component.html
+++ b/projects/nshmp-apps/src/app/gmm/spectra/components/plots/plots.component.html
@@ -1,6 +1,7 @@
 <!-- Spectra -->
 <div *ngIf="spectraPlotData$ | async" class="nshmp-plotly-plot">
   <plotly-plot
+    class="spectra-plot"
     [config]="(spectraPlotData$ | async)?.config"
     [className]="(spectraPlotData$ | async)?.config?.className"
     [data]="(spectraPlotData$ | async)?.data"
@@ -21,6 +22,7 @@
 <!-- Sigma Plot -->
 <div *ngIf="sigmaPlotData$ | async" class="nshmp-plotly-plot">
   <plotly-plot
+    class="sigma-plot"
     [config]="(sigmaPlotData$ | async)?.config"
     [className]="(sigmaPlotData$ | async)?.config?.className"
     [data]="(sigmaPlotData$ | async)?.data"
diff --git a/projects/nshmp-apps/src/app/gmm/spectra/components/site-parameters/site-parameters.component.html b/projects/nshmp-apps/src/app/gmm/spectra/components/site-parameters/site-parameters.component.html
index cb2d863b87902defe569839818e885d203bfdadc..1b2f68b22cfb1ffd4f179d59ccb3091fbe61f99e 100644
--- a/projects/nshmp-apps/src/app/gmm/spectra/components/site-parameters/site-parameters.component.html
+++ b/projects/nshmp-apps/src/app/gmm/spectra/components/site-parameters/site-parameters.component.html
@@ -6,7 +6,7 @@
     <div class="settings-subsection--section grid-row grid-gap-sm">
       <!-- Site & Basin: Vs30 input -->
       <gmm-lib-gmm-multi-select
-        class="grid-col-6"
+        class="grid-col-6 vs30-input"
         [label]="vs30Label"
         [multiple]="vs30Multiple$ | async"
         [numberControl]="(controls$ | async)?.vs30"
@@ -18,7 +18,7 @@
       </gmm-lib-gmm-multi-select>
 
       <!-- Site & Basin: Z 1.0 input -->
-      <mat-form-field class="grid-col-3">
+      <mat-form-field class="grid-col-3 z1p0-input">
         <mat-label>Z<sub>1.0</sub> (km)</mat-label>
         <input
           matInput
@@ -37,7 +37,7 @@
       </mat-form-field>
 
       <!-- Site & Basin: Z 2.5 input -->
-      <mat-form-field class="grid-col-3">
+      <mat-form-field class="grid-col-3 z2p5-input">
         <mat-label>Z<sub>2.5</sub> (km)</mat-label>
         <input
           matInput
diff --git a/projects/nshmp-apps/src/app/gmm/spectra/components/source-parameters/source-parameters.component.html b/projects/nshmp-apps/src/app/gmm/spectra/components/source-parameters/source-parameters.component.html
index ac67f16198ab9631e04f6db053230e8506bd20be..41dca40d49aafc2ade1e38cc5cffdfeaf99e941c 100644
--- a/projects/nshmp-apps/src/app/gmm/spectra/components/source-parameters/source-parameters.component.html
+++ b/projects/nshmp-apps/src/app/gmm/spectra/components/source-parameters/source-parameters.component.html
@@ -5,7 +5,7 @@
 
     <div class="settings-subsection--section grid-row grid-gap-sm">
       <!-- Source Parameters: zTop input -->
-      <mat-form-field class="grid-col-4">
+      <mat-form-field class="grid-col-4 ztop-input">
         <mat-label>Z<sub>TOR</sub> (km)</mat-label>
         <input
           matInput
@@ -24,7 +24,7 @@
       </mat-form-field>
 
       <!-- Source Parameters: Dip input -->
-      <mat-form-field class="grid-col-4">
+      <mat-form-field class="grid-col-4 dip-input">
         <mat-label>Dip (°)</mat-label>
         <input
           matInput
@@ -43,7 +43,7 @@
       </mat-form-field>
 
       <!-- Source Parameters: Width input -->
-      <mat-form-field class="grid-col-4">
+      <mat-form-field class="grid-col-4 width-input">
         <mat-label>Width (km)</mat-label>
         <input
           matInput
diff --git a/projects/nshmp-apps/src/app/hazard/disagg/components/control-panel/control-panel.component.html b/projects/nshmp-apps/src/app/hazard/disagg/components/control-panel/control-panel.component.html
index 6f4f075d8133fc3c480685b04f71dd094364a7da..424dc54a2d727871d0c97b4fb513d59b4854f95a 100644
--- a/projects/nshmp-apps/src/app/hazard/disagg/components/control-panel/control-panel.component.html
+++ b/projects/nshmp-apps/src/app/hazard/disagg/components/control-panel/control-panel.component.html
@@ -14,6 +14,7 @@
 
     <!-- Latitude -->
     <hazard-lib-location-form
+      class="latitude-input"
       [controlState]="formState?.controls?.latitude"
       label="Latitude"
       [bounds]="(facade.usage$ | async)?.response?.latitude"
@@ -22,6 +23,7 @@
 
     <!-- Longitude -->
     <hazard-lib-location-form
+      class="longitude-input"
       [controlState]="formState?.controls?.longitude"
       label="Longitude"
       [bounds]="(facade.usage$ | async)?.response?.longitude"
@@ -50,7 +52,7 @@
     </div>
 
     <!-- Imt -->
-    <mat-form-field class="grid-col-12">
+    <mat-form-field class="grid-col-12 imt-select">
       <mat-label> Intensity Measure Type </mat-label>
       <mat-select [ngrxFormControlState]="formState?.controls?.imt">
         <mat-option *ngFor="let imt of (sourceModel$ | async)?.imts" [value]="imt?.value">
diff --git a/projects/nshmp-apps/src/app/hazard/disagg/components/plots/plots.component.html b/projects/nshmp-apps/src/app/hazard/disagg/components/plots/plots.component.html
index c13d43d836f614a20cedcfe52a2900d1e8cf53ae..613f2b4c974fe71a3367d9a62cd1892525ddae07 100644
--- a/projects/nshmp-apps/src/app/hazard/disagg/components/plots/plots.component.html
+++ b/projects/nshmp-apps/src/app/hazard/disagg/components/plots/plots.component.html
@@ -1,7 +1,7 @@
 <div class="plot-container" class="grid-container-widescreen" *ngIf="formState$ | async">
   <div>
     <!-- Select component -->
-    <mat-form-field class="grid-col-12 padding-top-4">
+    <mat-form-field class="grid-col-12 padding-top-4 component-select">
       <mat-label> Component </mat-label>
       <mat-select
         [ngrxFormControlState]="(formState$ | async)?.controls?.disaggComponent"
@@ -16,6 +16,7 @@
     <ng-container *ngIf="componentData$ | async as componentData; else loading">
       <div *ngIf="(formState$ | async)?.value as formValues">
         <button
+          class="export-button"
           mat-raised-button
           color="primary"
           [disabled]="(disaggData$ | async) === null"
@@ -28,7 +29,12 @@
 
     <ng-template #loading>
       <div>
-        <button mat-raised-button color="primary" [disabled]="(disaggData$ | async) === null">
+        <button
+          mat-raised-button
+          class="export-button-loading"
+          color="primary"
+          [disabled]="(disaggData$ | async) === null"
+        >
           Export Data as CSV
         </button>
       </div>
@@ -41,7 +47,7 @@
     ></hazard-lib-disagg-plot>
   </div>
 
-  <div class="padding-bottom-4">
+  <div class="padding-bottom-4 summary">
     <h3>
       Summary statistics for, Disaggregation
       <span *ngIf="componentData$ | async">
@@ -51,6 +57,7 @@
     <mat-accordion multi>
       <!-- Summary report -->
       <mat-expansion-panel
+        class="summary-report"
         [expanded]="disaggData$ | async"
         [disabled]="(disaggData$ | async) === null"
       >
@@ -60,6 +67,7 @@
 
       <!-- Contributions -->
       <mat-expansion-panel
+        class="contributions"
         [expanded]="(componentData$ | async) !== null"
         [disabled]="(componentData$ | async) === null"
       >
diff --git a/projects/nshmp-apps/src/app/hazard/dynamic/components/content/content.component.html b/projects/nshmp-apps/src/app/hazard/dynamic/components/content/content.component.html
index 307099403349f30f4cc97cf83c66e4d860b79d95..c6ef854dba1ad4ebd00b79425a25b4236ed77526 100644
--- a/projects/nshmp-apps/src/app/hazard/dynamic/components/content/content.component.html
+++ b/projects/nshmp-apps/src/app/hazard/dynamic/components/content/content.component.html
@@ -1,10 +1,10 @@
 <mat-tab-group class="height-full" (selectedTabChange)="facade.plotRedraw()">
-  <mat-tab label="Plots">
+  <mat-tab labelClass="plots-tab" label="Plots">
     <div>
       <app-plots></app-plots>
     </div>
   </mat-tab>
-  <mat-tab label="Hazard Curve Data">
+  <mat-tab labelClass="hazard-tab" label="Hazard Curve Data">
     <div>
       <nshmp-lib-export-data-table
         [table]="hazardTableData$ | async"
@@ -15,7 +15,7 @@
       <nshmp-lib-data-table [table]="hazardTableData$ | async"> </nshmp-lib-data-table>
     </div>
   </mat-tab>
-  <mat-tab label="Response Spectrum Data">
+  <mat-tab labelClass="spectra-tab" label="Response Spectrum Data">
     <div>
       <nshmp-lib-export-data-table
         [table]="spectraTableData$ | async"
diff --git a/projects/nshmp-apps/src/app/hazard/dynamic/components/content/content.component.ts b/projects/nshmp-apps/src/app/hazard/dynamic/components/content/content.component.ts
index 089d8dfcbc41196437caaf819ed9396c28d7345a..9a54bd9cf9d0f74330eb72f4a8f1362289d9d66f 100644
--- a/projects/nshmp-apps/src/app/hazard/dynamic/components/content/content.component.ts
+++ b/projects/nshmp-apps/src/app/hazard/dynamic/components/content/content.component.ts
@@ -44,7 +44,7 @@ export class ContentComponent {
   );
 
   spectraFileName$ = this.facade.form$.pipe(
-    map(form => `response-spectra-${form.value.sourceType}-${form.value.vs30}`)
+    map(form => `response-spectra-${form.value.sourceType}-${form.value.vs30}.csv`)
   );
 
   constructor(public facade: DynamicHazardAppFacade) {}
diff --git a/projects/nshmp-apps/src/app/hazard/dynamic/components/control/control.component.html b/projects/nshmp-apps/src/app/hazard/dynamic/components/control/control.component.html
index d02f3efe5f43e8f38bf613801c832e4e43e6f37b..a2c358daa570460802d672aa7b4f44bb7a2bb346 100644
--- a/projects/nshmp-apps/src/app/hazard/dynamic/components/control/control.component.html
+++ b/projects/nshmp-apps/src/app/hazard/dynamic/components/control/control.component.html
@@ -14,6 +14,7 @@
 
     <!-- Latitude -->
     <hazard-lib-location-form
+      class="latitude-input"
       [controlState]="formState?.controls?.latitude"
       label="Latitude"
       [bounds]="(facade.usage$ | async)?.response?.latitude"
@@ -22,6 +23,7 @@
 
     <!-- Longitude -->
     <hazard-lib-location-form
+      class="longitude-input"
       [controlState]="formState?.controls?.longitude"
       label="Longitude"
       [bounds]="(facade.usage$ | async)?.response?.longitude"
@@ -57,7 +59,7 @@
     </hazard-lib-return-period-form>
 
     <!-- Source Type Control -->
-    <mat-form-field class="grid-col-12">
+    <mat-form-field class="grid-col-12 source-type-select">
       <mat-label>Source Type</mat-label>
       <mat-select [ngrxFormControlState]="formState?.controls?.sourceType">
         <mat-option *ngFor="let sourceType of sourceTypes$ | async" [value]="sourceType">
diff --git a/projects/nshmp-apps/src/app/hazard/dynamic/components/plots/plots.component.html b/projects/nshmp-apps/src/app/hazard/dynamic/components/plots/plots.component.html
index 17e24e18516542c0562b42f573a171c4f80dab43..6daa44f1d6491bc63b3f17c04feb098cea5d1de9 100644
--- a/projects/nshmp-apps/src/app/hazard/dynamic/components/plots/plots.component.html
+++ b/projects/nshmp-apps/src/app/hazard/dynamic/components/plots/plots.component.html
@@ -1,6 +1,7 @@
 <!-- Hazard Curves -->
 <div class="grid-container nshmp-plotly-plot">
   <plotly-plot
+    class="hazard-plot"
     #hazardPlot
     [divId]="(hazardPlotData$ | async)?.id"
     [config]="(hazardPlotData$ | async)?.config"
@@ -22,6 +23,7 @@
 <!-- Response Spectrum -->
 <div class="grid-container nshmp-plotly-plot">
   <plotly-plot
+    class="spectra-plot"
     #spectrumPlot
     [divId]="(spectrumPlotData$ | async)?.id"
     [config]="(spectrumPlotData$ | async)?.config"
diff --git a/projects/nshmp-apps/src/app/hazard/dynamic/components/settings/settings.component.html b/projects/nshmp-apps/src/app/hazard/dynamic/components/settings/settings.component.html
index 681d2b523e4cefad72a88d4ad898dd98730740ef..d4bf2800679f8817d692df4bca88db5fa0bd28d8 100644
--- a/projects/nshmp-apps/src/app/hazard/dynamic/components/settings/settings.component.html
+++ b/projects/nshmp-apps/src/app/hazard/dynamic/components/settings/settings.component.html
@@ -1,26 +1,36 @@
-<!-- Hazard plot settings -->
-<plot-lib-plot-settings *ngIf="hazardPlot$ | async" [plot]="hazardPlot$ | async">
-</plot-lib-plot-settings>
+<div class="height-full overflow-auto">
+  <!-- Hazard plot settings -->
+  <plot-lib-plot-settings
+    class="hazard-settings"
+    *ngIf="hazardPlot$ | async"
+    [plot]="hazardPlot$ | async"
+  >
+  </plot-lib-plot-settings>
 
-<!-- Response spectrum plot settings -->
-<plot-lib-plot-settings *ngIf="spectrumPlot$ | async" [plot]="spectrumPlot$ | async">
-</plot-lib-plot-settings>
+  <!-- Response spectrum plot settings -->
+  <plot-lib-plot-settings
+    class="spectra-settings"
+    *ngIf="spectrumPlot$ | async"
+    [plot]="spectrumPlot$ | async"
+  >
+  </plot-lib-plot-settings>
 
-<div class="padding-y-3"></div>
+  <div class="padding-y-3"></div>
 
-<!-- Reset button -->
-<div class="form-buttons form-buttons--right">
-  <button
-    mat-raised-button
-    color="warn"
-    [disabled]="
-      (hazardSettings$ | async)?.isPristine &&
-      (hazardSettings$ | async)?.isUntouched &&
-      (spectrumSettings$ | async)?.isPristine &&
-      (spectrumSettings$ | async)?.isUntouched
-    "
-    (click)="facade.resetSettings()"
-  >
-    Reset
-  </button>
+  <!-- Reset button -->
+  <div class="form-buttons form-buttons--right">
+    <button
+      mat-raised-button
+      color="warn"
+      [disabled]="
+        (hazardSettings$ | async)?.isPristine &&
+        (hazardSettings$ | async)?.isUntouched &&
+        (spectrumSettings$ | async)?.isPristine &&
+        (spectrumSettings$ | async)?.isUntouched
+      "
+      (click)="facade.resetSettings()"
+    >
+      Reset
+    </button>
+  </div>
 </div>
diff --git a/projects/nshmp-apps/src/app/hazard/dynamic/utils/response-handler.util.ts b/projects/nshmp-apps/src/app/hazard/dynamic/utils/response-handler.util.ts
index 345a4cbb6df0c039ad252772eccff74be7f92111..2ae560d305cf5e95ab93b5cff83cfe21685bd6a6 100644
--- a/projects/nshmp-apps/src/app/hazard/dynamic/utils/response-handler.util.ts
+++ b/projects/nshmp-apps/src/app/hazard/dynamic/utils/response-handler.util.ts
@@ -88,12 +88,7 @@ export const responseSpectra = (
   });
 
   return {
-    siteClass: {
-      display: form.siteClass,
-      displayorder: 0,
-      id: 0,
-      value: form.siteClass,
-    },
+    siteClass: form.siteClass,
     responseSpectrum,
     imts: [...hazardCurves.keys()],
   };
diff --git a/projects/nshmp-apps/src/app/hazard/static/components/content/content.component.html b/projects/nshmp-apps/src/app/hazard/static/components/content/content.component.html
index c14d3ae9aa884653b6e9b3fcac830c90d0e9bcb1..db31a1dddc3b592dc5a9e1e47508559ba97a91df 100644
--- a/projects/nshmp-apps/src/app/hazard/static/components/content/content.component.html
+++ b/projects/nshmp-apps/src/app/hazard/static/components/content/content.component.html
@@ -1,15 +1,15 @@
 <mat-tab-group class="height-full" (selectedTabChange)="staticHazardFacade.plotRedraw()">
-  <mat-tab label="Plots">
+  <mat-tab labelClass="plots-tab" label="Plots">
     <div>
       <app-plots></app-plots>
     </div>
   </mat-tab>
-  <mat-tab label="Hazard Curve Data">
+  <mat-tab labelClass="hazard-tab" label="Hazard Curve Data">
     <div>
       <app-curve-data></app-curve-data>
     </div>
   </mat-tab>
-  <mat-tab label="Response Spectrum Data">
+  <mat-tab labelClass="spectra-tab" label="Response Spectrum Data">
     <div>
       <app-spectrum-data></app-spectrum-data>
     </div>
diff --git a/projects/nshmp-apps/src/app/hazard/static/components/control-panel/control-panel.component.html b/projects/nshmp-apps/src/app/hazard/static/components/control-panel/control-panel.component.html
index 5bd9b365ae9663c5d1ce60b64a972ef06b30a0fb..0d9c2866309ec7084f93518587c7a313d9dff28d 100644
--- a/projects/nshmp-apps/src/app/hazard/static/components/control-panel/control-panel.component.html
+++ b/projects/nshmp-apps/src/app/hazard/static/components/control-panel/control-panel.component.html
@@ -14,6 +14,7 @@
 
     <!-- Latitude -->
     <hazard-lib-location-form
+      class="latitude-input"
       [controlState]="formState?.controls?.latitude"
       label="Latitude"
       [bounds]="(facade.usage$ | async)?.response?.latitude"
@@ -22,6 +23,7 @@
 
     <!-- Longitude -->
     <hazard-lib-location-form
+      class="longitude-input"
       [controlState]="formState?.controls?.longitude"
       label="Longitude"
       [bounds]="(facade.usage$ | async)?.response?.longitude"
diff --git a/projects/nshmp-apps/src/app/hazard/static/components/curve-data/curve-data.component.html b/projects/nshmp-apps/src/app/hazard/static/components/curve-data/curve-data.component.html
index 96021a771462809e04063ead04e26452a05a3571..8b39b1ce2288dca674848fb0daf2055e635e5678 100644
--- a/projects/nshmp-apps/src/app/hazard/static/components/curve-data/curve-data.component.html
+++ b/projects/nshmp-apps/src/app/hazard/static/components/curve-data/curve-data.component.html
@@ -9,10 +9,10 @@
 <mat-accordion *ngIf="serviceResponse$ | async">
   <mat-expansion-panel
     *ngFor="let table of tables$ | async"
-    [expanded]="(siteClass$ | async) === table.siteClass.value"
+    [expanded]="(siteClass$ | async) === table.siteClass"
   >
     <mat-expansion-panel-header>
-      {{ table.siteClass.display }}
+      {{ table.siteClass }}
     </mat-expansion-panel-header>
 
     <ng-template matExpansionPanelContent>
diff --git a/projects/nshmp-apps/src/app/hazard/static/components/curve-data/curve-data.component.ts b/projects/nshmp-apps/src/app/hazard/static/components/curve-data/curve-data.component.ts
index 189d6edd96d1341e66470d1d75d81d476a922ee1..3653bdc62c1f427a2ea036796e80154f4e0f4d22 100644
--- a/projects/nshmp-apps/src/app/hazard/static/components/curve-data/curve-data.component.ts
+++ b/projects/nshmp-apps/src/app/hazard/static/components/curve-data/curve-data.component.ts
@@ -20,7 +20,7 @@ export class CurveDataComponent {
   hazardFilename$ = this.facade.form$.pipe(
     map(form => {
       const values = form.value;
-      return `static-hazard-curves-${values.model}-(${values.longitude},${values.latitude})`;
+      return `static-hazard-curves-${values.model}-(${values.longitude},${values.latitude}).csv`;
     })
   );
   serviceResponse$ = this.facade.serviceResponse$;
@@ -69,7 +69,7 @@ export class CurveDataComponent {
         td: xy.xs.map(x => (x ? x.toFixed(this.dataPrecision) : x)),
       };
       if (index === 0) {
-        xData.exportSectionBreakLabel = `Site class - ${response.metadata.siteClass.display}`;
+        xData.exportSectionBreakLabel = `Site class - ${response.metadata.siteClass}`;
       }
       tableData.push(xData);
 
diff --git a/projects/nshmp-apps/src/app/hazard/static/components/plots/plots.component.html b/projects/nshmp-apps/src/app/hazard/static/components/plots/plots.component.html
index 0df2719897bd08d3f05417dd2994f8d4a1e1ab9b..5193f11871d40981231062b6c0d45a6082430844 100644
--- a/projects/nshmp-apps/src/app/hazard/static/components/plots/plots.component.html
+++ b/projects/nshmp-apps/src/app/hazard/static/components/plots/plots.component.html
@@ -1,6 +1,7 @@
 <!-- Hazard Curves -->
 <div class="grid-container nshmp-plotly-plot" *ngIf="hazardPlotData$ | async">
   <plotly-plot
+    class="hazard-plot"
     [divId]="(hazardPlotData$ | async)?.id"
     [config]="(hazardPlotData$ | async)?.config"
     [data]="(hazardPlotData$ | async)?.data"
@@ -21,6 +22,7 @@
 <!-- Response Spectrum -->
 <div class="grid-container nshmp-plotly-plot" *ngIf="responseSpectrumPlotData$ | async">
   <plotly-plot
+    class="spectra-plot"
     [divId]="(responseSpectrumPlotData$ | async)?.id"
     [config]="(responseSpectrumPlotData$ | async)?.config"
     [data]="(responseSpectrumPlotData$ | async)?.data"
diff --git a/projects/nshmp-apps/src/app/hazard/static/components/settings/settings.component.html b/projects/nshmp-apps/src/app/hazard/static/components/settings/settings.component.html
index 7ad0fc586a73897cf44e91c085f68a5333b66980..bcaf765f7eddfec633cbaeb70a21c95d645bfbc1 100644
--- a/projects/nshmp-apps/src/app/hazard/static/components/settings/settings.component.html
+++ b/projects/nshmp-apps/src/app/hazard/static/components/settings/settings.component.html
@@ -1,26 +1,36 @@
-<!-- Hazard plot settings -->
-<plot-lib-plot-settings *ngIf="hazardPlot$ | async" [plot]="hazardPlot$ | async">
-</plot-lib-plot-settings>
+<div class="height-full overflow-auto">
+  <!-- Hazard plot settings -->
+  <plot-lib-plot-settings
+    class="hazard-settings"
+    *ngIf="hazardPlot$ | async"
+    [plot]="hazardPlot$ | async"
+  >
+  </plot-lib-plot-settings>
 
-<!-- Response spectrum plot settings -->
-<plot-lib-plot-settings *ngIf="spectrumPlot$ | async" [plot]="spectrumPlot$ | async">
-</plot-lib-plot-settings>
+  <!-- Response spectrum plot settings -->
+  <plot-lib-plot-settings
+    class="spectra-settings"
+    *ngIf="spectrumPlot$ | async"
+    [plot]="spectrumPlot$ | async"
+  >
+  </plot-lib-plot-settings>
 
-<div class="padding-y-3"></div>
+  <div class="padding-y-3"></div>
 
-<!-- Reset button -->
-<div class="form-buttons form-buttons--right">
-  <button
-    mat-raised-button
-    color="warn"
-    [disabled]="
-      (hazardSettings$ | async)?.isPristine &&
-      (hazardSettings$ | async)?.isUntouched &&
-      (spectrumSettings$ | async)?.isPristine &&
-      (spectrumSettings$ | async)?.isUntouched
-    "
-    (click)="staticHazardFacade.resetSettings()"
-  >
-    Reset
-  </button>
+  <!-- Reset button -->
+  <div class="form-buttons form-buttons--right">
+    <button
+      mat-raised-button
+      color="warn"
+      [disabled]="
+        (hazardSettings$ | async)?.isPristine &&
+        (hazardSettings$ | async)?.isUntouched &&
+        (spectrumSettings$ | async)?.isPristine &&
+        (spectrumSettings$ | async)?.isUntouched
+      "
+      (click)="staticHazardFacade.resetSettings()"
+    >
+      Reset
+    </button>
+  </div>
 </div>
diff --git a/projects/nshmp-apps/src/app/hazard/static/components/spectrum-data/spectrum-data.component.html b/projects/nshmp-apps/src/app/hazard/static/components/spectrum-data/spectrum-data.component.html
index cc1f7da0b64634d8a1e5b1c94afedaf39d06464b..754fae9c08e68c5cd1071a75c14c94f992544ddb 100644
--- a/projects/nshmp-apps/src/app/hazard/static/components/spectrum-data/spectrum-data.component.html
+++ b/projects/nshmp-apps/src/app/hazard/static/components/spectrum-data/spectrum-data.component.html
@@ -9,10 +9,10 @@
 <mat-accordion *ngIf="responseSpectra$ | async">
   <mat-expansion-panel
     *ngFor="let table of tables$ | async"
-    [expanded]="(siteClass$ | async) === table.siteClass.value"
+    [expanded]="(siteClass$ | async) === table.siteClass"
   >
     <mat-expansion-panel-header>
-      {{ table.siteClass.display }}
+      {{ table.siteClass }}
     </mat-expansion-panel-header>
 
     <nshmp-lib-data-table [hideDownloadButton]="true" [table]="table.tableData">
diff --git a/projects/nshmp-apps/src/app/hazard/static/components/spectrum-data/spectrum-data.component.ts b/projects/nshmp-apps/src/app/hazard/static/components/spectrum-data/spectrum-data.component.ts
index 64aeb0b2af43f02330bb77c6c64259a313d376bf..9569e82b45872ed2088e9eea39f4706c47bd2d72 100644
--- a/projects/nshmp-apps/src/app/hazard/static/components/spectrum-data/spectrum-data.component.ts
+++ b/projects/nshmp-apps/src/app/hazard/static/components/spectrum-data/spectrum-data.component.ts
@@ -21,7 +21,7 @@ export class SpectrumDataComponent {
   spectraFilename$ = this.facade.form$.pipe(
     map(form => {
       const values = form.value;
-      return `static-hazard-spectra-${values.model}-(${values.longitude},${values.latitude})`;
+      return `static-hazard-spectra-${values.model}-(${values.longitude},${values.latitude}).csv`;
     })
   );
   tables$ = this.facade.responseSpectra$.pipe(map(spectra => this.spectraToTableData(spectra)));
@@ -34,7 +34,7 @@ export class SpectrumDataComponent {
     return spectra.map(spectrum => {
       const tableData = responseSpectraToTableData(spectrum).map((data, index) => {
         if (index === 0) {
-          data.exportSectionBreakLabel = `Site Class - ${spectrum.siteClass.display}`;
+          data.exportSectionBreakLabel = `Site Class - ${spectrum.siteClass}`;
         }
         return data;
       });
diff --git a/projects/nshmp-apps/src/app/hazard/static/models/curve-table.model.ts b/projects/nshmp-apps/src/app/hazard/static/models/curve-table.model.ts
index 5c932a8ff25538806255729a1013b4f9952c877f..3998b12b5f24e04fddbad0aecdf28f3343061ec1 100644
--- a/projects/nshmp-apps/src/app/hazard/static/models/curve-table.model.ts
+++ b/projects/nshmp-apps/src/app/hazard/static/models/curve-table.model.ts
@@ -1,10 +1,9 @@
-import {nshmpWsUtils} from '@ghsc/nshmp-utils';
 import {TableData} from 'projects/nshmp-lib/src/lib/models/table-data.model';
 
 /**
  * Static hazard curve data table, table per site class
  */
 export interface StaticHazardCurveTable {
-  siteClass: nshmpWsUtils.metadata.EnumParameterValues;
+  siteClass: string;
   tableData: TableData[];
 }
diff --git a/projects/nshmp-apps/src/app/hazard/static/utils/response.handler.ts b/projects/nshmp-apps/src/app/hazard/static/utils/response.handler.ts
index 626932212148d352cc0963285a1b1c3c799fde37..14f3f3fc6ae5fe1c25c4231cdc4fbece673694af 100644
--- a/projects/nshmp-apps/src/app/hazard/static/utils/response.handler.ts
+++ b/projects/nshmp-apps/src/app/hazard/static/utils/response.handler.ts
@@ -28,7 +28,7 @@ export const getResponseData = (
   const siteClass = state.form.controls.siteClass.value;
   return state.serviceResponses
     ? state.serviceResponses.response.find(response => {
-        return response.find(data => data.metadata.siteClass.value === siteClass);
+        return response.find(data => data.metadata.siteClass === siteClass);
       })
     : null;
 };
diff --git a/projects/nshmp-apps/src/app/source-models/data/components/control-panel/control-panel.component.html b/projects/nshmp-apps/src/app/source-models/data/components/control-panel/control-panel.component.html
index 39888fd3984f6091534efe6a9f48b053f6ce43df..dccec8f9abef3c267013488c2cda01197278cfa1 100644
--- a/projects/nshmp-apps/src/app/source-models/data/components/control-panel/control-panel.component.html
+++ b/projects/nshmp-apps/src/app/source-models/data/components/control-panel/control-panel.component.html
@@ -2,11 +2,12 @@
   [ngrxFormState]="form$ | async"
   *ngIf="(form$ | async) && (faultSectionUsage$ | async)"
   (submit)="facade.callServices()"
+  class="height-full overflow-auto"
 >
   <div class="settings-section">
     <mat-label class="settings-section--label">Fault Sections</mat-label>
 
-    <mat-form-field class="grid-col-12 padding-top-2">
+    <mat-form-field class="grid-col-12 padding-top-2 fault-section-select">
       <mat-label>Group Name</mat-label>
       <mat-select [ngrxFormControlState]="(faultSectionForm$ | async)?.group">
         <mat-optgroup label="">
@@ -43,7 +44,10 @@
     </mat-form-field>
   </div> -->
 
-  <div class="form-buttons form-buttons--left">
-    <button mat-raised-button color="primary" class="plot-button" type="submit">Plot</button>
-  </div>
+  <nshmp-lib-control-panel-buttons
+    [plotDisabled]="(form$ | async)?.isInvalid"
+    [showServiceCallInfo]="false"
+    [showReset]="false"
+  >
+  </nshmp-lib-control-panel-buttons>
 </form>
diff --git a/projects/nshmp-lib/src/lib/components/control-panel-buttons/control-panel-buttons.component.html b/projects/nshmp-lib/src/lib/components/control-panel-buttons/control-panel-buttons.component.html
index dbb036c880eda7e2ab020c66b8168c2aa8818c57..bc51288b64e0f08cf9ac1e2e07eecb3bc6607e88 100644
--- a/projects/nshmp-lib/src/lib/components/control-panel-buttons/control-panel-buttons.component.html
+++ b/projects/nshmp-lib/src/lib/components/control-panel-buttons/control-panel-buttons.component.html
@@ -1,6 +1,6 @@
 <div class="form-buttons form-buttons--left grid-row">
   <!-- Plot Button -->
-  <div class="{{ plotButtonSize }} padding-x-1">
+  <div class="{{ plotButtonSize }} padding-x-1 submit-button">
     <button
       class="grid-col-12"
       color="primary"
@@ -14,7 +14,7 @@
 
   <!-- Service Info Button -->
   <div
-    class="{{ serviceCallButtonSize }} padding-x-1"
+    class="{{ serviceCallButtonSize }} padding-x-1 service-button"
     [ngClass]="{'grid-offset-3': showReset === false}"
     *ngIf="showServiceCallInfo"
   >
@@ -32,7 +32,7 @@
   <div class="grid-col-fill" *ngIf="showServiceCallInfo === false"></div>
 
   <!-- Reset Control Panel Button -->
-  <div class="{{ resetButtonSize }} padding-x-1" *ngIf="showReset">
+  <div class="{{ resetButtonSize }} padding-x-1 reset-button" *ngIf="showReset">
     <button
       class="grid-col-12"
       color="warn"
diff --git a/projects/nshmp-lib/src/lib/utils/navigation.utils.ts b/projects/nshmp-lib/src/lib/utils/navigation.utils.ts
index 46bb67dce141cc830834e2f31ec60b65f82b5a34..3498940bacaf6e01e46e8f11d20a8532d855c1d0 100644
--- a/projects/nshmp-lib/src/lib/utils/navigation.utils.ts
+++ b/projects/nshmp-lib/src/lib/utils/navigation.utils.ts
@@ -132,7 +132,7 @@ export function devNavigation(): NavigationList[] {
       ],
     },
     {
-      subHeader: 'AWS Apps',
+      subHeader: 'AWS',
       navigation: devAwsApps(),
     },
   ];
diff --git a/projects/plot-lib/src/lib/components/plot-settings-axis/plot-settings-axis.component.html b/projects/plot-lib/src/lib/components/plot-settings-axis/plot-settings-axis.component.html
index 26868184c772b8d0eb4f6ba7b5fa127d68d9d3ca..a3559bb88e89e5d09d2b0b8b1e29b8d70b3be2b4 100644
--- a/projects/plot-lib/src/lib/components/plot-settings-axis/plot-settings-axis.component.html
+++ b/projects/plot-lib/src/lib/components/plot-settings-axis/plot-settings-axis.component.html
@@ -4,13 +4,13 @@
 
   <div class="settings-subsection--section">
     <!-- Plot Settings: Axis: title text -->
-    <mat-form-field class="grid-col-12">
+    <mat-form-field class="grid-col-12 axis-title-input">
       <mat-label>Title</mat-label>
       <input type="text" matInput [ngrxFormControlState]="settings?.title?.controls?.text" />
     </mat-form-field>
 
     <!-- Plot Settings: Axis: title font size -->
-    <mat-form-field class="grid-col-12">
+    <mat-form-field class="grid-col-12 axis-title-font-size-input">
       <mat-label>Title Font Size</mat-label>
       <input
         matInput
@@ -20,7 +20,7 @@
     </mat-form-field>
 
     <!-- Plot Settings: Axis: axis type -->
-    <div class="mat-form-field-wrapper">
+    <div class="mat-form-field-wrapper axis-type-toggle">
       <mat-button-toggle-group
         class="grid-col-12"
         [ngrxFormControlState]="settings?.axis?.controls?.type"
diff --git a/projects/plot-lib/src/lib/components/plot-settings/plot-settings.component.html b/projects/plot-lib/src/lib/components/plot-settings/plot-settings.component.html
index dbc9d9ffec7aa9c573f060b00dce0fc5a82d877c..79526209cd078e4948686320685eb8bfd242bb20 100644
--- a/projects/plot-lib/src/lib/components/plot-settings/plot-settings.component.html
+++ b/projects/plot-lib/src/lib/components/plot-settings/plot-settings.component.html
@@ -4,13 +4,13 @@
     <mat-label class="settings-section--label">{{ nshmpPlot.label }}</mat-label>
 
     <!-- Plot Settings: title text -->
-    <mat-form-field class="grid-col-12 padding-top-2">
+    <mat-form-field class="grid-col-12 padding-top-2 title-input">
       <mat-label>Title</mat-label>
       <input type="text" matInput [ngrxFormControlState]="plotSettings?.title?.controls?.text" />
     </mat-form-field>
 
     <!-- Plot Settings: title font size -->
-    <mat-form-field class="grid-col-12">
+    <mat-form-field class="grid-col-12 title-font-size-input">
       <mat-label>Title Font Size</mat-label>
       <input
         matInput
@@ -20,7 +20,7 @@
     </mat-form-field>
 
     <!-- Plot Settings: height-->
-    <mat-form-field class="grid-col-12 padding-bottom-2">
+    <mat-form-field class="grid-col-12 padding-bottom-2 plot-height-input">
       <mat-label>Height</mat-label>
       <input
         type="number"
@@ -34,17 +34,19 @@
     <!-- Plot Settings: show legend -->
     <mat-slide-toggle
       color="primary"
-      class="margin-left-1 margin-bottom-2"
+      class="margin-left-1 margin-bottom-2 show-legend-toggle"
       [ngrxFormControlState]="plotSettings?.layout?.controls?.showlegend"
     >
       Legend
     </mat-slide-toggle>
 
     <!-- X-Axis Settings-->
-    <plot-lib-plot-settings-axis [settings]="plotSettings?.xAxis"> </plot-lib-plot-settings-axis>
+    <plot-lib-plot-settings-axis class="x-axis-settings" [settings]="plotSettings?.xAxis">
+    </plot-lib-plot-settings-axis>
 
     <!-- Y-Axis Settings -->
-    <plot-lib-plot-settings-axis [settings]="plotSettings?.yAxis"> </plot-lib-plot-settings-axis>
+    <plot-lib-plot-settings-axis class="y-axis-settings" [settings]="plotSettings?.yAxis">
+    </plot-lib-plot-settings-axis>
 
     <mat-divider class="padding-bottom-2"></mat-divider>
   </form>