diff --git a/angular.json b/angular.json
index d162d74..9cd2dc4 100644
--- a/angular.json
+++ b/angular.json
@@ -31,6 +31,7 @@
"src/assets"
],
"styles": [
+ "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
"src/styles.scss"
],
"scripts": []
@@ -99,6 +100,7 @@
"src/assets"
],
"styles": [
+ "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
"src/styles.scss"
],
"scripts": []
diff --git a/package-lock.json b/package-lock.json
index de47f0e..e2d96de 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,13 +9,21 @@
"version": "0.0.0",
"dependencies": {
"@angular/animations": "~12.2.0",
+ "@angular/cdk": "^12.2.13",
"@angular/common": "~12.2.0",
"@angular/compiler": "~12.2.0",
"@angular/core": "~12.2.0",
+ "@angular/flex-layout": "^12.0.0-beta.35",
"@angular/forms": "~12.2.0",
+ "@angular/material": "^12.2.13",
"@angular/platform-browser": "~12.2.0",
"@angular/platform-browser-dynamic": "~12.2.0",
"@angular/router": "~12.2.0",
+ "@fortawesome/angular-fontawesome": "^0.10.1",
+ "@fortawesome/fontawesome-svg-core": "~1.2.36",
+ "@fortawesome/free-brands-svg-icons": "^5.15.4",
+ "@fortawesome/free-regular-svg-icons": "^5.15.4",
+ "@fortawesome/free-solid-svg-icons": "^5.15.4",
"rxjs": "~6.6.0",
"tslib": "^2.3.0",
"zone.js": "~0.11.4"
@@ -289,6 +297,28 @@
"@angular/core": "12.2.13"
}
},
+ "node_modules/@angular/cdk": {
+ "version": "12.2.13",
+ "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-12.2.13.tgz",
+ "integrity": "sha512-zSKRhECyFqhingIeyRInIyTvYErt4gWo+x5DQr0b7YLUbU8DZSwWnG4w76Ke2s4U8T7ry1jpJBHoX/e8YBpGMg==",
+ "dependencies": {
+ "tslib": "^2.2.0"
+ },
+ "optionalDependencies": {
+ "parse5": "^5.0.0"
+ },
+ "peerDependencies": {
+ "@angular/common": "^12.0.0 || ^13.0.0-0",
+ "@angular/core": "^12.0.0 || ^13.0.0-0",
+ "rxjs": "^6.5.3 || ^7.0.0"
+ }
+ },
+ "node_modules/@angular/cdk/node_modules/parse5": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz",
+ "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==",
+ "optional": true
+ },
"node_modules/@angular/cli": {
"version": "12.2.13",
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-12.2.13.tgz",
@@ -410,6 +440,21 @@
"zone.js": "~0.11.4"
}
},
+ "node_modules/@angular/flex-layout": {
+ "version": "12.0.0-beta.35",
+ "resolved": "https://registry.npmjs.org/@angular/flex-layout/-/flex-layout-12.0.0-beta.35.tgz",
+ "integrity": "sha512-nPi2MGDFuCacwWHqxF/G7lUJd2X99HbLjjUvKXnyLwyCIVgH1sfS52su2wYbVYWJRqAVAB2/VMlrtW8Khr8hDA==",
+ "dependencies": {
+ "tslib": "^2.1.0"
+ },
+ "peerDependencies": {
+ "@angular/cdk": "^12.0.0",
+ "@angular/common": ">=12.0.0",
+ "@angular/core": ">=12.0.0",
+ "@angular/platform-browser": ">=12.0.0",
+ "rxjs": "^6.0.0 || ^7.0.0"
+ }
+ },
"node_modules/@angular/forms": {
"version": "12.2.13",
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-12.2.13.tgz",
@@ -427,6 +472,22 @@
"rxjs": "^6.5.3 || ^7.0.0"
}
},
+ "node_modules/@angular/material": {
+ "version": "12.2.13",
+ "resolved": "https://registry.npmjs.org/@angular/material/-/material-12.2.13.tgz",
+ "integrity": "sha512-6g2GyN4qp2D+DqY2AwrQuPB3cd9gybvQVXvNRbTPXEulHr+LgGei00ySdFHFp6RvdGSMZ4i3LM1Fq3VkFxhCfQ==",
+ "dependencies": {
+ "tslib": "^2.2.0"
+ },
+ "peerDependencies": {
+ "@angular/animations": "^12.0.0 || ^13.0.0-0",
+ "@angular/cdk": "12.2.13",
+ "@angular/common": "^12.0.0 || ^13.0.0-0",
+ "@angular/core": "^12.0.0 || ^13.0.0-0",
+ "@angular/forms": "^12.0.0 || ^13.0.0-0",
+ "rxjs": "^6.5.3 || ^7.0.0"
+ }
+ },
"node_modules/@angular/platform-browser": {
"version": "12.2.13",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-12.2.13.tgz",
@@ -2251,6 +2312,74 @@
"node": ">=10.0.0"
}
},
+ "node_modules/@fortawesome/angular-fontawesome": {
+ "version": "0.10.1",
+ "resolved": "https://registry.npmjs.org/@fortawesome/angular-fontawesome/-/angular-fontawesome-0.10.1.tgz",
+ "integrity": "sha512-4nVRm+NcLcdaNrNFhThb/7/tb5CDm0vQwJFyljR3XMCQyEr94hMX5SiUYbvYm9YJVImMYfdpR0lO0B8sh12Vbw==",
+ "dependencies": {
+ "tslib": "^2.3.1"
+ },
+ "peerDependencies": {
+ "@fortawesome/fontawesome-svg-core": "~1.2.27 || ~1.3.0-beta2"
+ }
+ },
+ "node_modules/@fortawesome/fontawesome-common-types": {
+ "version": "0.2.36",
+ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz",
+ "integrity": "sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg==",
+ "hasInstallScript": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@fortawesome/fontawesome-svg-core": {
+ "version": "1.2.36",
+ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.36.tgz",
+ "integrity": "sha512-YUcsLQKYb6DmaJjIHdDWpBIGCcyE/W+p/LMGvjQem55Mm2XWVAP5kWTMKWLv9lwpCVjpLxPyOMOyUocP1GxrtA==",
+ "hasInstallScript": true,
+ "dependencies": {
+ "@fortawesome/fontawesome-common-types": "^0.2.36"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@fortawesome/free-brands-svg-icons": {
+ "version": "5.15.4",
+ "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-5.15.4.tgz",
+ "integrity": "sha512-f1witbwycL9cTENJegcmcZRYyawAFbm8+c6IirLmwbbpqz46wyjbQYLuxOc7weXFXfB7QR8/Vd2u5R3q6JYD9g==",
+ "hasInstallScript": true,
+ "dependencies": {
+ "@fortawesome/fontawesome-common-types": "^0.2.36"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@fortawesome/free-regular-svg-icons": {
+ "version": "5.15.4",
+ "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.15.4.tgz",
+ "integrity": "sha512-9VNNnU3CXHy9XednJ3wzQp6SwNwT3XaM26oS4Rp391GsxVYA+0oDR2J194YCIWf7jNRCYKjUCOduxdceLrx+xw==",
+ "hasInstallScript": true,
+ "dependencies": {
+ "@fortawesome/fontawesome-common-types": "^0.2.36"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@fortawesome/free-solid-svg-icons": {
+ "version": "5.15.4",
+ "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.4.tgz",
+ "integrity": "sha512-JLmQfz6tdtwxoihXLg6lT78BorrFyCf59SAwBM6qV/0zXyVeDygJVb3fk+j5Qat+Yvcxp1buLTY5iDh1ZSAQ8w==",
+ "hasInstallScript": true,
+ "dependencies": {
+ "@fortawesome/fontawesome-common-types": "^0.2.36"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/@istanbuljs/schema": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
@@ -16189,6 +16318,23 @@
"tslib": "^2.2.0"
}
},
+ "@angular/cdk": {
+ "version": "12.2.13",
+ "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-12.2.13.tgz",
+ "integrity": "sha512-zSKRhECyFqhingIeyRInIyTvYErt4gWo+x5DQr0b7YLUbU8DZSwWnG4w76Ke2s4U8T7ry1jpJBHoX/e8YBpGMg==",
+ "requires": {
+ "parse5": "^5.0.0",
+ "tslib": "^2.2.0"
+ },
+ "dependencies": {
+ "parse5": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz",
+ "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==",
+ "optional": true
+ }
+ }
+ },
"@angular/cli": {
"version": "12.2.13",
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-12.2.13.tgz",
@@ -16270,6 +16416,14 @@
"tslib": "^2.2.0"
}
},
+ "@angular/flex-layout": {
+ "version": "12.0.0-beta.35",
+ "resolved": "https://registry.npmjs.org/@angular/flex-layout/-/flex-layout-12.0.0-beta.35.tgz",
+ "integrity": "sha512-nPi2MGDFuCacwWHqxF/G7lUJd2X99HbLjjUvKXnyLwyCIVgH1sfS52su2wYbVYWJRqAVAB2/VMlrtW8Khr8hDA==",
+ "requires": {
+ "tslib": "^2.1.0"
+ }
+ },
"@angular/forms": {
"version": "12.2.13",
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-12.2.13.tgz",
@@ -16278,6 +16432,14 @@
"tslib": "^2.2.0"
}
},
+ "@angular/material": {
+ "version": "12.2.13",
+ "resolved": "https://registry.npmjs.org/@angular/material/-/material-12.2.13.tgz",
+ "integrity": "sha512-6g2GyN4qp2D+DqY2AwrQuPB3cd9gybvQVXvNRbTPXEulHr+LgGei00ySdFHFp6RvdGSMZ4i3LM1Fq3VkFxhCfQ==",
+ "requires": {
+ "tslib": "^2.2.0"
+ }
+ },
"@angular/platform-browser": {
"version": "12.2.13",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-12.2.13.tgz",
@@ -17571,6 +17733,51 @@
"integrity": "sha512-Fxt+AfXgjMoin2maPIYzFZnQjAXjAL0PHscM5pRTtatFqB+vZxAM9tLp2Optnuw3QOQC40jTNeGYFOMvyf7v9g==",
"dev": true
},
+ "@fortawesome/angular-fontawesome": {
+ "version": "0.10.1",
+ "resolved": "https://registry.npmjs.org/@fortawesome/angular-fontawesome/-/angular-fontawesome-0.10.1.tgz",
+ "integrity": "sha512-4nVRm+NcLcdaNrNFhThb/7/tb5CDm0vQwJFyljR3XMCQyEr94hMX5SiUYbvYm9YJVImMYfdpR0lO0B8sh12Vbw==",
+ "requires": {
+ "tslib": "^2.3.1"
+ }
+ },
+ "@fortawesome/fontawesome-common-types": {
+ "version": "0.2.36",
+ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz",
+ "integrity": "sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg=="
+ },
+ "@fortawesome/fontawesome-svg-core": {
+ "version": "1.2.36",
+ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.36.tgz",
+ "integrity": "sha512-YUcsLQKYb6DmaJjIHdDWpBIGCcyE/W+p/LMGvjQem55Mm2XWVAP5kWTMKWLv9lwpCVjpLxPyOMOyUocP1GxrtA==",
+ "requires": {
+ "@fortawesome/fontawesome-common-types": "^0.2.36"
+ }
+ },
+ "@fortawesome/free-brands-svg-icons": {
+ "version": "5.15.4",
+ "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-5.15.4.tgz",
+ "integrity": "sha512-f1witbwycL9cTENJegcmcZRYyawAFbm8+c6IirLmwbbpqz46wyjbQYLuxOc7weXFXfB7QR8/Vd2u5R3q6JYD9g==",
+ "requires": {
+ "@fortawesome/fontawesome-common-types": "^0.2.36"
+ }
+ },
+ "@fortawesome/free-regular-svg-icons": {
+ "version": "5.15.4",
+ "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.15.4.tgz",
+ "integrity": "sha512-9VNNnU3CXHy9XednJ3wzQp6SwNwT3XaM26oS4Rp391GsxVYA+0oDR2J194YCIWf7jNRCYKjUCOduxdceLrx+xw==",
+ "requires": {
+ "@fortawesome/fontawesome-common-types": "^0.2.36"
+ }
+ },
+ "@fortawesome/free-solid-svg-icons": {
+ "version": "5.15.4",
+ "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.4.tgz",
+ "integrity": "sha512-JLmQfz6tdtwxoihXLg6lT78BorrFyCf59SAwBM6qV/0zXyVeDygJVb3fk+j5Qat+Yvcxp1buLTY5iDh1ZSAQ8w==",
+ "requires": {
+ "@fortawesome/fontawesome-common-types": "^0.2.36"
+ }
+ },
"@istanbuljs/schema": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
diff --git a/package.json b/package.json
index 70ec04b..f095f4c 100644
--- a/package.json
+++ b/package.json
@@ -11,13 +11,21 @@
"private": true,
"dependencies": {
"@angular/animations": "~12.2.0",
+ "@angular/cdk": "^12.2.13",
"@angular/common": "~12.2.0",
"@angular/compiler": "~12.2.0",
"@angular/core": "~12.2.0",
+ "@angular/flex-layout": "^12.0.0-beta.35",
"@angular/forms": "~12.2.0",
+ "@angular/material": "^12.2.13",
"@angular/platform-browser": "~12.2.0",
"@angular/platform-browser-dynamic": "~12.2.0",
"@angular/router": "~12.2.0",
+ "@fortawesome/angular-fontawesome": "^0.10.1",
+ "@fortawesome/fontawesome-svg-core": "~1.2.36",
+ "@fortawesome/free-brands-svg-icons": "^5.15.4",
+ "@fortawesome/free-regular-svg-icons": "^5.15.4",
+ "@fortawesome/free-solid-svg-icons": "^5.15.4",
"rxjs": "~6.6.0",
"tslib": "^2.3.0",
"zone.js": "~0.11.4"
diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts
new file mode 100644
index 0000000..fc19897
--- /dev/null
+++ b/src/app/app-routing.module.ts
@@ -0,0 +1,23 @@
+import { NgModule } from '@angular/core';
+import { Routes, RouterModule } from '@angular/router'; // CLI imports router
+import { GameComponent } from './games/game/game.component';
+import { LandingComponent } from './landing/landing.component'
+import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
+
+const appRoutes: Routes = [
+ {
+ path: "",
+ component: LandingComponent
+ },
+ {
+ path: "404",
+ component: PageNotFoundComponent
+ }
+]; // sets up routes constant where you define your routes
+
+// configures NgModule imports and exports
+@NgModule({
+ imports: [RouterModule.forRoot(appRoutes)],
+ exports: [RouterModule]
+})
+export class AppRoutingModule { }
diff --git a/src/app/app.component.html b/src/app/app.component.html
index 33752f3..eafb0a3 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -1,500 +1,9 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
{{ title }} app is running!
-
-
-
-
-
-
-
Resources
-
Here are some links to help you get started:
-
-
-
-
-
Next Steps
-
What do you want to do next with your app?
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
ng generate component xyz
-
ng add @angular/material
-
ng add @angular/pwa
-
ng add _____
-
ng test
-
ng build
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
diff --git a/src/app/app.component.scss b/src/app/app.component.scss
index e69de29..7394c43 100644
--- a/src/app/app.component.scss
+++ b/src/app/app.component.scss
@@ -0,0 +1,15 @@
+$footer-height: 125px;
+
+:host {
+ display: flex;
+ flex-direction: column;
+ min-height: 100%;
+}
+
+main {
+ /*background: url("/assets/img/background.png");
+ background-attachment: fixed;
+ background-size: cover;*/
+ flex: 1;
+ padding: 1em;
+}
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index ab5f79a..07b5f6b 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -5,6 +5,4 @@ import { Component } from '@angular/core';
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
-export class AppComponent {
- title = 'zelda64';
-}
+export class AppComponent {}
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 8dfc1d6..4bc8f4a 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -2,13 +2,40 @@ import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { HttpClientModule } from '@angular/common/http';
+import { MatButtonModule } from '@angular/material/button';
+import { MatMenuModule } from '@angular/material/menu';
+import { MatToolbarModule } from '@angular/material/toolbar';
+import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
+import { GamesModule } from './games/games.module';
+import { ToolbarComponent } from './toolbar/toolbar.component';
+import { N64BoxComponent } from './common-components/n64-box/n64-box.component';
+import { FlexLayoutModule } from '@angular/flex-layout';
+import { AppRoutingModule } from './app-routing.module';
+import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
+import { FooterComponent } from './footer/footer.component';
+import { LandingComponent } from './landing/landing.component';
@NgModule({
declarations: [
- AppComponent
+ AppComponent,
+ ToolbarComponent,
+ LandingComponent,
+ FooterComponent,
+ PageNotFoundComponent
],
imports: [
- BrowserModule
+ BrowserModule,
+ BrowserAnimationsModule,
+ GamesModule,
+ AppRoutingModule,
+ HttpClientModule,
+ MatButtonModule,
+ MatMenuModule,
+ MatToolbarModule,
+ FontAwesomeModule,
+ FlexLayoutModule
],
providers: [],
bootstrap: [AppComponent]
diff --git a/src/app/common-components/common-components.module.ts b/src/app/common-components/common-components.module.ts
new file mode 100644
index 0000000..67411a6
--- /dev/null
+++ b/src/app/common-components/common-components.module.ts
@@ -0,0 +1,24 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { N64BoxComponent } from './n64-box/n64-box.component';
+import { N64ButtonComponent } from './n64-button/n64-button.component';
+import { RouterModule } from '@angular/router';
+
+/**
+ * Module that holds generic content box and button components of varying styles.
+ */
+@NgModule({
+ declarations: [
+ N64BoxComponent,
+ N64ButtonComponent
+ ],
+ imports: [
+ CommonModule,
+ RouterModule
+ ],
+ exports: [
+ N64BoxComponent,
+ N64ButtonComponent
+ ]
+})
+export class CommonComponentsModule { }
diff --git a/src/app/common-components/n64-box/n64-box.component.html b/src/app/common-components/n64-box/n64-box.component.html
new file mode 100644
index 0000000..38f3e98
--- /dev/null
+++ b/src/app/common-components/n64-box/n64-box.component.html
@@ -0,0 +1,14 @@
+
+
+ File {{ index }}
+
+ {{ heading }}
+
+
+
+
+
+
+
+
+
diff --git a/src/app/common-components/n64-box/n64-box.component.scss b/src/app/common-components/n64-box/n64-box.component.scss
new file mode 100644
index 0000000..45626ed
--- /dev/null
+++ b/src/app/common-components/n64-box/n64-box.component.scss
@@ -0,0 +1,102 @@
+$border-width: 6px;
+$color-bg: #2f487c;
+$color-bg-hl: #3f67cd;
+$color-bd: #1c2a49;
+$color-bd-hl: #5f87dd;
+$font-size-heading: 22px;
+$font-size-content: 18px;
+$slot-size: 105px;
+
+section {
+ background: linear-gradient(120deg,
+ $color-bg 10%,
+ $color-bg-hl 30%,
+ $color-bg-hl 40%,
+ $color-bg 60%,
+ $color-bg-hl 80%,
+ $color-bg);
+}
+
+* {
+ box-sizing: border-box;
+ color: white;
+ position: relative;
+}
+
+.box {
+ border-color: $color-bd-hl $color-bd $color-bd $color-bd-hl;
+ border-style: solid;
+ border-width: $border-width;
+}
+
+header {
+ display: flex;
+ width: 100%;
+}
+
+header div.slot {
+ background: linear-gradient(90deg,
+ $color-bg,
+ $color-bg-hl 30%,
+ $color-bg 80%,
+ $color-bg-hl);
+ color: black;
+ cursor: default;
+ font-size: $font-size-heading;
+ min-width: $slot-size;
+ padding: 10px 14px 14px 14px;
+ text-align: center;
+ text-shadow: 2px 2px 2px $color-bd-hl;
+ width: $slot-size;
+}
+
+header div.heading {
+ border-bottom-style: none;
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+ font-kerning: none;
+ font-size: $font-size-heading;
+ flex-grow: 1;
+ height: 63px;
+ padding: 14px;
+ width: auto;
+
+ hr {
+ border-color: $color-bd;
+ border-width: 2px;
+ }
+}
+
+.hack-slot {
+ border-color: $color-bd-hl $color-bd $color-bd $color-bd-hl;
+ border-right-style: none;
+ border-bottom-style: none;
+ border-top-right-radius: 0;
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+ float: left;
+ height: 0;
+ padding: 0;
+ width: $slot-size;
+}
+
+.hack-heading {
+ border-color: $color-bd-hl $color-bd $color-bd $color-bd-hl;
+ border-top-style: none;
+ border-bottom-style: none;
+ border-radius: 0;
+ height: $border-width;
+ margin-left: $slot-size;
+ padding: 0;
+ position: relative;
+ width: auto;
+}
+
+article {
+ border-top-style: none !important;
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+ padding: 14px;
+ width: 100%;
+ font-size: $font-size-content;
+}
diff --git a/src/app/common-components/n64-box/n64-box.component.spec.ts b/src/app/common-components/n64-box/n64-box.component.spec.ts
new file mode 100644
index 0000000..d6711bf
--- /dev/null
+++ b/src/app/common-components/n64-box/n64-box.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { N64BoxComponent } from './n64-box.component';
+
+describe('N64BoxComponent', () => {
+ let component: N64BoxComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ N64BoxComponent ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(N64BoxComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/common-components/n64-box/n64-box.component.ts b/src/app/common-components/n64-box/n64-box.component.ts
new file mode 100644
index 0000000..761db76
--- /dev/null
+++ b/src/app/common-components/n64-box/n64-box.component.ts
@@ -0,0 +1,25 @@
+import { Component, Input, OnInit } from '@angular/core';
+
+/**
+ * Box used for content. In the style of OoT/MM file select menu.
+ */
+@Component({
+ selector: 'n64-box',
+ templateUrl: './n64-box.component.html',
+ styleUrls: ['./n64-box.component.scss']
+})
+export class N64BoxComponent {
+
+ constructor() { }
+
+ /**
+ * The index to display in the top left.
+ */
+ @Input() index: number = 1;
+
+ /**
+ * The heading on the box.
+ */
+ @Input() heading: string;
+
+}
diff --git a/src/app/common-components/n64-button/n64-button.component.html b/src/app/common-components/n64-button/n64-button.component.html
new file mode 100644
index 0000000..5aec2b8
--- /dev/null
+++ b/src/app/common-components/n64-button/n64-button.component.html
@@ -0,0 +1,3 @@
+
diff --git a/src/app/common-components/n64-button/n64-button.component.scss b/src/app/common-components/n64-button/n64-button.component.scss
new file mode 100644
index 0000000..c31335e
--- /dev/null
+++ b/src/app/common-components/n64-button/n64-button.component.scss
@@ -0,0 +1,35 @@
+$border-width: 6px;
+$brightness-factor: 1.25;
+$color-bg: #2f487c;
+$color-bg-hl: #3f67cd;
+$color-bd: #1c2a49;
+$color-bd-hl: #5f87dd;
+$size: 105px;
+
+button {
+ background: linear-gradient(90deg,
+ $color-bg,
+ $color-bg-hl 30%,
+ $color-bg 80%,
+ $color-bg-hl);
+ border-color: $color-bd-hl $color-bd $color-bd $color-bd-hl;
+ border-style: solid;
+ border-width: $border-width;
+ box-sizing: border-box;
+ color: black;
+ cursor: default;
+ font-size: 22px;
+ min-width: $size;
+ padding: 10px 14px 14px 14px;
+ text-align: center;
+ text-shadow: 2px 2px 2px $color-bd-hl;
+}
+
+button:hover, button:focus {
+ filter: brightness($brightness-factor);
+}
+
+button:active {
+ border-color: $color-bd $color-bd-hl $color-bd-hl $color-bd;
+ filter: brightness(1.0 / $brightness-factor);
+}
diff --git a/src/app/common-components/n64-button/n64-button.component.spec.ts b/src/app/common-components/n64-button/n64-button.component.spec.ts
new file mode 100644
index 0000000..da3a90e
--- /dev/null
+++ b/src/app/common-components/n64-button/n64-button.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { N64ButtonComponent } from './n64-button.component';
+
+describe('N64ButtonComponent', () => {
+ let component: N64ButtonComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ N64ButtonComponent ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(N64ButtonComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/common-components/n64-button/n64-button.component.ts b/src/app/common-components/n64-button/n64-button.component.ts
new file mode 100644
index 0000000..5710347
--- /dev/null
+++ b/src/app/common-components/n64-button/n64-button.component.ts
@@ -0,0 +1,20 @@
+import { Component, Input, OnInit } from '@angular/core';
+
+/**
+ * Button in the style of OoT/MM file select.
+ */
+@Component({
+ selector: 'n64-button',
+ templateUrl: './n64-button.component.html',
+ styleUrls: ['./n64-button.component.scss']
+})
+export class N64ButtonComponent {
+
+ constructor() { }
+
+ /**
+ * The location to take the user on click.
+ */
+ @Input() routerLink: string | any[];
+
+}
diff --git a/src/app/footer/footer.component.html b/src/app/footer/footer.component.html
new file mode 100644
index 0000000..2937fb1
--- /dev/null
+++ b/src/app/footer/footer.component.html
@@ -0,0 +1,10 @@
+
+
+ Made with <3 by the Zelda RET
+
+
+
+
+
+
+
diff --git a/src/app/footer/footer.component.scss b/src/app/footer/footer.component.scss
new file mode 100644
index 0000000..a4e2111
--- /dev/null
+++ b/src/app/footer/footer.component.scss
@@ -0,0 +1,3 @@
+mat-toolbar-row {
+ text-align: center;
+}
diff --git a/src/app/footer/footer.component.spec.ts b/src/app/footer/footer.component.spec.ts
new file mode 100644
index 0000000..a3c4af9
--- /dev/null
+++ b/src/app/footer/footer.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { FooterComponent } from './footer.component';
+
+describe('FooterComponent', () => {
+ let component: FooterComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ FooterComponent ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(FooterComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/footer/footer.component.ts b/src/app/footer/footer.component.ts
new file mode 100644
index 0000000..b796b3b
--- /dev/null
+++ b/src/app/footer/footer.component.ts
@@ -0,0 +1,21 @@
+import { Component, OnInit } from '@angular/core';
+import { faDiscord } from '@fortawesome/free-brands-svg-icons';
+
+/**
+ * Application footer.
+ */
+@Component({
+ selector: 'app-footer',
+ templateUrl: './footer.component.html',
+ styleUrls: ['./footer.component.scss']
+})
+export class FooterComponent {
+
+ constructor() { }
+
+ /**
+ * Icon definition.
+ */
+ discord = faDiscord;
+
+}
diff --git a/src/app/games/game-summaries/game-summaries.component.html b/src/app/games/game-summaries/game-summaries.component.html
new file mode 100644
index 0000000..3291ff7
--- /dev/null
+++ b/src/app/games/game-summaries/game-summaries.component.html
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/app/games/game-summaries/game-summaries.component.scss b/src/app/games/game-summaries/game-summaries.component.scss
new file mode 100644
index 0000000..793e7a6
--- /dev/null
+++ b/src/app/games/game-summaries/game-summaries.component.scss
@@ -0,0 +1,9 @@
+div {
+ margin-right: -1em;
+}
+
+game-summary {
+ flex-grow: 0;
+ margin-bottom: 12px;
+ margin-right: 1em;
+}
diff --git a/src/app/games/game-summaries/game-summaries.component.spec.ts b/src/app/games/game-summaries/game-summaries.component.spec.ts
new file mode 100644
index 0000000..423c2a8
--- /dev/null
+++ b/src/app/games/game-summaries/game-summaries.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { GameSummariesComponent } from './game-summaries.component';
+
+describe('GameSummariesComponent', () => {
+ let component: GameSummariesComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ GameSummariesComponent ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(GameSummariesComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/games/game-summaries/game-summaries.component.ts b/src/app/games/game-summaries/game-summaries.component.ts
new file mode 100644
index 0000000..a289986
--- /dev/null
+++ b/src/app/games/game-summaries/game-summaries.component.ts
@@ -0,0 +1,29 @@
+import { Component, OnInit } from '@angular/core';
+import { GamesService } from '../games.service';
+import { ISummary } from '../games.service.interface';
+
+/**
+ * Container for all game summaries.
+ */
+@Component({
+ selector: 'game-summaries',
+ templateUrl: './game-summaries.component.html',
+ styleUrls: ['./game-summaries.component.scss']
+})
+export class GameSummariesComponent implements OnInit {
+
+ constructor(private gamesService: GamesService) { }
+
+ /**
+ * List of summaries to use for "GameSummary" components.
+ */
+ summaries: ISummary[] = [];
+
+ ngOnInit(): void {
+ this.gamesService.getSummaries().subscribe(
+ res => this.summaries = res,
+ err => console.error(err)
+ )
+ }
+
+}
diff --git a/src/app/games/game-summaries/game-summary/game-summary.component.html b/src/app/games/game-summaries/game-summary/game-summary.component.html
new file mode 100644
index 0000000..40c64ae
--- /dev/null
+++ b/src/app/games/game-summaries/game-summary/game-summary.component.html
@@ -0,0 +1,19 @@
+
+
+
+
+ {{ data.title }}
+
+ Percent completion: {{ (data.progress * 100) }}%
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/games/game-summaries/game-summary/game-summary.component.scss b/src/app/games/game-summaries/game-summary/game-summary.component.scss
new file mode 100644
index 0000000..0c6c651
--- /dev/null
+++ b/src/app/games/game-summaries/game-summary/game-summary.component.scss
@@ -0,0 +1,12 @@
+mat-card > a {
+ text-decoration: none;
+}
+
+img[mat-card-avatar] {
+ border-radius: 0;
+ object-fit: contain;
+}
+
+mat-card-actions {
+ text-align: center;
+}
diff --git a/src/app/games/game-summaries/game-summary/game-summary.component.spec.ts b/src/app/games/game-summaries/game-summary/game-summary.component.spec.ts
new file mode 100644
index 0000000..dd3f8de
--- /dev/null
+++ b/src/app/games/game-summaries/game-summary/game-summary.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { GameSummaryComponent } from './game-summary.component';
+
+describe('GameSummaryComponent', () => {
+ let component: GameSummaryComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ GameSummaryComponent ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(GameSummaryComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/games/game-summaries/game-summary/game-summary.component.ts b/src/app/games/game-summaries/game-summary/game-summary.component.ts
new file mode 100644
index 0000000..6fd046c
--- /dev/null
+++ b/src/app/games/game-summaries/game-summary/game-summary.component.ts
@@ -0,0 +1,34 @@
+import { Component, Input, OnInit } from '@angular/core';
+import { ISummary } from '../../games.service.interface';
+import { faGithub, faTrello } from '@fortawesome/free-brands-svg-icons';
+
+/**
+ * Displays a game's name, decomp progress, and links.
+ */
+@Component({
+ selector: 'game-summary',
+ templateUrl: './game-summary.component.html',
+ styleUrls: ['./game-summary.component.scss']
+})
+export class GameSummaryComponent implements OnInit {
+
+ constructor() { }
+
+ /**
+ * The summary to use.
+ */
+ @Input() data: ISummary;
+
+ /**
+ * Icon definition.
+ */
+ github = faGithub;
+ /**
+ * Icon definition.
+ */
+ trello = faTrello;
+
+ ngOnInit(): void {
+ }
+
+}
diff --git a/src/app/games/game/game-intro/game-intro.component.html b/src/app/games/game/game-intro/game-intro.component.html
new file mode 100644
index 0000000..e6636e3
--- /dev/null
+++ b/src/app/games/game/game-intro/game-intro.component.html
@@ -0,0 +1 @@
+game-intro works!
diff --git a/src/app/games/game/game-intro/game-intro.component.scss b/src/app/games/game/game-intro/game-intro.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/games/game/game-intro/game-intro.component.spec.ts b/src/app/games/game/game-intro/game-intro.component.spec.ts
new file mode 100644
index 0000000..fc2a8aa
--- /dev/null
+++ b/src/app/games/game/game-intro/game-intro.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { GameIntroComponent } from './game-intro.component';
+
+describe('GameIntroComponent', () => {
+ let component: GameIntroComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ GameIntroComponent ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(GameIntroComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/games/game/game-intro/game-intro.component.ts b/src/app/games/game/game-intro/game-intro.component.ts
new file mode 100644
index 0000000..c58a550
--- /dev/null
+++ b/src/app/games/game/game-intro/game-intro.component.ts
@@ -0,0 +1,18 @@
+import { Component, OnInit } from '@angular/core';
+
+/**
+ * Introduction to a game and general projects.
+ */
+@Component({
+ selector: 'game-intro',
+ templateUrl: './game-intro.component.html',
+ styleUrls: ['./game-intro.component.scss']
+})
+export class GameIntroComponent implements OnInit {
+
+ constructor() { }
+
+ ngOnInit(): void {
+ }
+
+}
diff --git a/src/app/games/game/game-projects/game-projects.component.html b/src/app/games/game/game-projects/game-projects.component.html
new file mode 100644
index 0000000..560f24a
--- /dev/null
+++ b/src/app/games/game/game-projects/game-projects.component.html
@@ -0,0 +1 @@
+game-projects works!
diff --git a/src/app/games/game/game-projects/game-projects.component.scss b/src/app/games/game/game-projects/game-projects.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/games/game/game-projects/game-projects.component.spec.ts b/src/app/games/game/game-projects/game-projects.component.spec.ts
new file mode 100644
index 0000000..be54f05
--- /dev/null
+++ b/src/app/games/game/game-projects/game-projects.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { GameProjectsComponent } from './game-projects.component';
+
+describe('GameProgressComponent', () => {
+ let component: GameProjectsComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ GameProjectsComponent ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(GameProjectsComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/games/game/game-projects/game-projects.component.ts b/src/app/games/game/game-projects/game-projects.component.ts
new file mode 100644
index 0000000..75f4f96
--- /dev/null
+++ b/src/app/games/game/game-projects/game-projects.component.ts
@@ -0,0 +1,18 @@
+import { Component, OnInit } from '@angular/core';
+
+/**
+ * Shows all of the projects for a game.
+ */
+@Component({
+ selector: 'game-projects',
+ templateUrl: './game-projects.component.html',
+ styleUrls: ['./game-projects.component.scss']
+})
+export class GameProjectsComponent implements OnInit {
+
+ constructor() { }
+
+ ngOnInit(): void {
+ }
+
+}
diff --git a/src/app/games/game/game-resources/game-resources.component.html b/src/app/games/game/game-resources/game-resources.component.html
new file mode 100644
index 0000000..d9fcbda
--- /dev/null
+++ b/src/app/games/game/game-resources/game-resources.component.html
@@ -0,0 +1 @@
+game-resources works!
diff --git a/src/app/games/game/game-resources/game-resources.component.scss b/src/app/games/game/game-resources/game-resources.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/games/game/game-resources/game-resources.component.spec.ts b/src/app/games/game/game-resources/game-resources.component.spec.ts
new file mode 100644
index 0000000..a6c4060
--- /dev/null
+++ b/src/app/games/game/game-resources/game-resources.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { GameResourcesComponent } from './game-resources.component';
+
+describe('GameResourcesComponent', () => {
+ let component: GameResourcesComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ GameResourcesComponent ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(GameResourcesComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/games/game/game-resources/game-resources.component.ts b/src/app/games/game/game-resources/game-resources.component.ts
new file mode 100644
index 0000000..be3cee5
--- /dev/null
+++ b/src/app/games/game/game-resources/game-resources.component.ts
@@ -0,0 +1,18 @@
+import { Component, OnInit } from '@angular/core';
+
+/**
+ * Shows the resources associated with a game.
+ */
+@Component({
+ selector: 'game-resources',
+ templateUrl: './game-resources.component.html',
+ styleUrls: ['./game-resources.component.scss']
+})
+export class GameResourcesComponent implements OnInit {
+
+ constructor() { }
+
+ ngOnInit(): void {
+ }
+
+}
diff --git a/src/app/games/game/game-tabs/game-tabs.component.html b/src/app/games/game/game-tabs/game-tabs.component.html
new file mode 100644
index 0000000..f8d046f
--- /dev/null
+++ b/src/app/games/game/game-tabs/game-tabs.component.html
@@ -0,0 +1,3 @@
+Intro
+Projects
+Resources
diff --git a/src/app/games/game/game-tabs/game-tabs.component.scss b/src/app/games/game/game-tabs/game-tabs.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/games/game/game-tabs/game-tabs.component.spec.ts b/src/app/games/game/game-tabs/game-tabs.component.spec.ts
new file mode 100644
index 0000000..5882d5e
--- /dev/null
+++ b/src/app/games/game/game-tabs/game-tabs.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { GameTabsComponent } from './game-tabs.component';
+
+describe('GameTabsComponent', () => {
+ let component: GameTabsComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ GameTabsComponent ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(GameTabsComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/games/game/game-tabs/game-tabs.component.ts b/src/app/games/game/game-tabs/game-tabs.component.ts
new file mode 100644
index 0000000..f5f2f9d
--- /dev/null
+++ b/src/app/games/game/game-tabs/game-tabs.component.ts
@@ -0,0 +1,32 @@
+import { Component, OnInit } from '@angular/core';
+import { GamesService } from '../../games.service';
+
+/**
+ * Allows user to swap between the GameIntro, GameProjects, and GameResources components.
+ */
+@Component({
+ selector: 'game-tabs',
+ templateUrl: './game-tabs.component.html',
+ styleUrls: ['./game-tabs.component.scss']
+})
+export class GameTabsComponent implements OnInit {
+
+ constructor(private gamesService: GamesService) { }
+
+ /**
+ * Determines whether the Intro tab is shown.
+ */
+ hasIntro: boolean = true;
+ /**
+ * Determines whether the Projects tab is shown.
+ */
+ hasProjects: boolean = true;
+ /**
+ * Determines whether the Resources tab is showing.
+ */
+ hasResources: boolean = true;
+
+ ngOnInit(): void {
+ }
+
+}
diff --git a/src/app/games/game/game.component.html b/src/app/games/game/game.component.html
new file mode 100644
index 0000000..238108d
--- /dev/null
+++ b/src/app/games/game/game.component.html
@@ -0,0 +1,7 @@
+{{ summary.title }}
+
+
+
+
diff --git a/src/app/games/game/game.component.scss b/src/app/games/game/game.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/games/game/game.component.spec.ts b/src/app/games/game/game.component.spec.ts
new file mode 100644
index 0000000..e99f1f7
--- /dev/null
+++ b/src/app/games/game/game.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { GameComponent } from './game.component';
+
+describe('GameComponent', () => {
+ let component: GameComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ GameComponent ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(GameComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/games/game/game.component.ts b/src/app/games/game/game.component.ts
new file mode 100644
index 0000000..3f772a1
--- /dev/null
+++ b/src/app/games/game/game.component.ts
@@ -0,0 +1,47 @@
+import { Component, OnInit } from '@angular/core';
+import { ActivatedRoute, Router } from '@angular/router';
+import { GamesService } from '../games.service';
+import { ISummary } from '../games.service.interface';
+
+/**
+ * Container component for the tabs and relevant content.
+ */
+@Component({
+ selector: 'game',
+ templateUrl: './game.component.html',
+ styleUrls: ['./game.component.scss']
+})
+export class GameComponent implements OnInit {
+
+ constructor(private gamesService: GamesService, private router: Router, private route: ActivatedRoute) { }
+
+ /**
+ * The summary to use to display basic game info.
+ */
+ summary: ISummary;
+
+ ngOnInit(): void {
+ this.route.params.subscribe(
+ res => {
+ const slug: string = res.slug;
+ this.gamesService.getSummaries().subscribe(
+ res => {
+ // redirect if an unknown slug is provided
+ const index = res.findIndex((summary) => summary.slug === slug)
+ if (index !== -1) {
+ this.summary = res[index];
+ }
+ else {
+ this.router.navigateByUrl("/404", { skipLocationChange: true });
+ }
+ },
+ err => {
+ console.error(err);
+ this.router.navigateByUrl("/404", { skipLocationChange: true });
+ }
+ );
+ }
+ );
+ }
+
+}
diff --git a/src/app/games/games-routing.module.ts b/src/app/games/games-routing.module.ts
new file mode 100644
index 0000000..e0e33a1
--- /dev/null
+++ b/src/app/games/games-routing.module.ts
@@ -0,0 +1,53 @@
+import { NgModule } from '@angular/core';
+import { Routes, RouterModule } from '@angular/router'; // CLI imports router
+import { GameIntroComponent } from './game/game-intro/game-intro.component';
+import { GameProjectsComponent } from './game/game-projects/game-projects.component';
+import { GameResourcesComponent } from './game/game-resources/game-resources.component';
+import { GameComponent } from './game/game.component';
+
+const gameRoutes: Routes = [
+ {
+ path: "games/:slug",
+ component: GameComponent,
+ children: [
+ {
+ path: "",
+ component: GameIntroComponent,
+ outlet: "game"
+ },
+ {
+ path: "intro",
+ redirectTo: ""
+ }
+ ]
+ },
+ {
+ path: "games/:slug/projects",
+ component: GameComponent,
+ children: [
+ {
+ path: "",
+ component: GameProjectsComponent,
+ outlet: "game"
+ }
+ ]
+ },
+ {
+ path: "games/:slug/resources",
+ component: GameComponent,
+ children: [
+ {
+ path: "",
+ component: GameResourcesComponent,
+ outlet: "game"
+ }
+ ]
+ }
+]; // sets up routes constant where you define your routes
+
+// configures NgModule imports and exports
+@NgModule({
+ imports: [RouterModule.forChild(gameRoutes)],
+ exports: [RouterModule]
+})
+export class GamesRoutingModule { }
diff --git a/src/app/games/games.module.ts b/src/app/games/games.module.ts
new file mode 100644
index 0000000..5809d30
--- /dev/null
+++ b/src/app/games/games.module.ts
@@ -0,0 +1,48 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { CommonComponentsModule } from '../common-components/common-components.module';
+import { environment } from 'src/environments/environment';
+import { GamesService } from './games.service';
+import { GameSummariesComponent } from './game-summaries/game-summaries.component';
+import { GameSummaryComponent } from './game-summaries/game-summary/game-summary.component';
+import { GameComponent } from './game/game.component';
+import { MatButtonModule } from '@angular/material/button';
+import { MatCardModule } from '@angular/material/card';
+import { MatGridListModule } from '@angular/material/grid-list';
+import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
+import { FlexLayoutModule } from '@angular/flex-layout';
+import { GameTabsComponent } from './game/game-tabs/game-tabs.component';
+import { GameIntroComponent } from './game/game-intro/game-intro.component';
+import { GameProjectsComponent } from './game/game-projects/game-projects.component';
+import { GameResourcesComponent } from './game/game-resources/game-resources.component';
+import { GamesRoutingModule } from './games-routing.module';
+
+/**
+ * Module for holding game related components and services.
+ */
+@NgModule({
+ declarations: [
+ GameSummariesComponent,
+ GameSummaryComponent,
+ GameComponent,
+ GameTabsComponent,
+ GameIntroComponent,
+ GameProjectsComponent,
+ GameResourcesComponent
+ ],
+ imports: [
+ CommonModule,
+ GamesRoutingModule,
+ CommonComponentsModule,
+ MatButtonModule,
+ MatCardModule,
+ MatGridListModule,
+ FontAwesomeModule,
+ FlexLayoutModule
+ ],
+ exports: [
+ GameSummariesComponent
+ ],
+ providers: [GamesService]
+})
+export class GamesModule { }
diff --git a/src/app/games/games.service.interface.ts b/src/app/games/games.service.interface.ts
new file mode 100644
index 0000000..463f9ec
--- /dev/null
+++ b/src/app/games/games.service.interface.ts
@@ -0,0 +1,75 @@
+import { Observable } from "rxjs";
+
+/**
+ * Basic game info to display on the front page.
+ */
+export interface ISummary {
+ /**
+ * The string to use to identify the game.
+ */
+ slug: string;
+ /**
+ * The game's full title.
+ */
+ title: string;
+ /**
+ * An introduction string.
+ */
+ intro: string;
+ /**
+ * A percentage for general decompilation progress.
+ * TODO Calculate this on the fly, as opposed to a static number.
+ */
+ progress: number;
+ /**
+ * Links to display as an icon. The key defines the icon, and the value is the link href.
+ */
+ links: {[type: string]: string};
+}
+
+/**
+ * Resources to display within the Resources tab.
+ */
+export interface IResource {
+ /**
+ * The slugs/games that this resource will appear under.
+ */
+ slugs: string[];
+ /**
+ * The header that this resource is grouped under.
+ */
+ header: string;
+ /**
+ * The subheader that this resource is grouped under.
+ */
+ subheader?: string;
+ /**
+ * The text to display on the webpage.
+ */
+ text: string;
+ /**
+ * The link to direct the user to.
+ */
+ link: string;
+ /**
+ * A description to display on mouseover.
+ */
+ tooltip?: string;
+}
+
+/**
+ * Functions to implement within "GamesService".
+ */
+export interface IGamesService {
+
+ /**
+ * Get the summaries for all supported games.
+ */
+ getSummaries(): Observable;
+
+ /**
+ * Get all of the resources.
+ */
+ getResources(): Observable;
+
+}
diff --git a/src/app/games/games.service.spec.ts b/src/app/games/games.service.spec.ts
new file mode 100644
index 0000000..56cb16f
--- /dev/null
+++ b/src/app/games/games.service.spec.ts
@@ -0,0 +1,16 @@
+import { TestBed } from '@angular/core/testing';
+
+import { GamesService } from './games.service';
+
+describe('GamesService', () => {
+ let service: GamesService;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({});
+ service = TestBed.inject(GamesService);
+ });
+
+ it('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/src/app/games/games.service.ts b/src/app/games/games.service.ts
new file mode 100644
index 0000000..a2b0a10
--- /dev/null
+++ b/src/app/games/games.service.ts
@@ -0,0 +1,23 @@
+import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { IGamesService, IResource, ISummary } from './games.service.interface';
+
+/**
+ * Obtains data for games.
+ */
+@Injectable({
+ providedIn: 'root'
+})
+export class GamesService implements IGamesService {
+
+ constructor(private http: HttpClient) { }
+
+ getSummaries(): Observable {
+ return this.http.get("/assets/json/summaries.json")
+ }
+
+ getResources(): Observable {
+ return this.http.get("/assets/json/resources.json")
+ }
+}
diff --git a/src/app/landing/landing.component.html b/src/app/landing/landing.component.html
new file mode 100644
index 0000000..d03b616
--- /dev/null
+++ b/src/app/landing/landing.component.html
@@ -0,0 +1,2 @@
+Zelda Reverse Engineering Team
+
diff --git a/src/app/landing/landing.component.scss b/src/app/landing/landing.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/landing/landing.component.spec.ts b/src/app/landing/landing.component.spec.ts
new file mode 100644
index 0000000..2eeadfe
--- /dev/null
+++ b/src/app/landing/landing.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { LandingComponent } from './landing.component';
+
+describe('LandingComponent', () => {
+ let component: LandingComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ LandingComponent ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(LandingComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/landing/landing.component.ts b/src/app/landing/landing.component.ts
new file mode 100644
index 0000000..6f688da
--- /dev/null
+++ b/src/app/landing/landing.component.ts
@@ -0,0 +1,15 @@
+import { Component, OnInit } from '@angular/core';
+
+/**
+ * Component shown on the front page (when people go to "/").
+ */
+@Component({
+ selector: 'landing',
+ templateUrl: './landing.component.html',
+ styleUrls: ['./landing.component.scss']
+})
+export class LandingComponent {
+
+ constructor() { }
+
+}
diff --git a/src/app/page-not-found/page-not-found.component.html b/src/app/page-not-found/page-not-found.component.html
new file mode 100644
index 0000000..885d4a1
--- /dev/null
+++ b/src/app/page-not-found/page-not-found.component.html
@@ -0,0 +1,2 @@
+Page Not Found
+The requested page cannot be found!
diff --git a/src/app/page-not-found/page-not-found.component.scss b/src/app/page-not-found/page-not-found.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/page-not-found/page-not-found.component.spec.ts b/src/app/page-not-found/page-not-found.component.spec.ts
new file mode 100644
index 0000000..aa242a9
--- /dev/null
+++ b/src/app/page-not-found/page-not-found.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { PageNotFoundComponent } from './page-not-found.component';
+
+describe('PageNotFoundComponent', () => {
+ let component: PageNotFoundComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ PageNotFoundComponent ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(PageNotFoundComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/page-not-found/page-not-found.component.ts b/src/app/page-not-found/page-not-found.component.ts
new file mode 100644
index 0000000..df45d65
--- /dev/null
+++ b/src/app/page-not-found/page-not-found.component.ts
@@ -0,0 +1,15 @@
+import { Component, OnInit } from '@angular/core';
+
+/**
+ * Page used to handle 404 errors.
+ */
+@Component({
+ selector: 'page-not-found',
+ templateUrl: './page-not-found.component.html',
+ styleUrls: ['./page-not-found.component.scss']
+})
+export class PageNotFoundComponent {
+
+ constructor() { }
+
+}
diff --git a/src/app/toolbar/toolbar.component.html b/src/app/toolbar/toolbar.component.html
new file mode 100644
index 0000000..3437e17
--- /dev/null
+++ b/src/app/toolbar/toolbar.component.html
@@ -0,0 +1,12 @@
+
+
+ Zelda RET
+
+
+
+
+
diff --git a/src/app/toolbar/toolbar.component.scss b/src/app/toolbar/toolbar.component.scss
new file mode 100644
index 0000000..e31729e
--- /dev/null
+++ b/src/app/toolbar/toolbar.component.scss
@@ -0,0 +1,11 @@
+a {
+ height: 90%;
+
+ img {
+ height: 100%;
+ }
+}
+
+#app-title {
+ margin: 0 1em;
+}
diff --git a/src/app/toolbar/toolbar.component.spec.ts b/src/app/toolbar/toolbar.component.spec.ts
new file mode 100644
index 0000000..3acf866
--- /dev/null
+++ b/src/app/toolbar/toolbar.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ToolbarComponent } from './toolbar.component';
+
+describe('ToolbarComponent', () => {
+ let component: ToolbarComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ ToolbarComponent ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(ToolbarComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/toolbar/toolbar.component.ts b/src/app/toolbar/toolbar.component.ts
new file mode 100644
index 0000000..c1de825
--- /dev/null
+++ b/src/app/toolbar/toolbar.component.ts
@@ -0,0 +1,29 @@
+import { Component, OnInit } from '@angular/core';
+import { GamesService } from '../games/games.service';
+import { ISummary } from '../games/games.service.interface';
+
+/**
+ * Application header.
+ */
+@Component({
+ selector: 'app-toolbar',
+ templateUrl: './toolbar.component.html',
+ styleUrls: ['./toolbar.component.scss']
+})
+export class ToolbarComponent implements OnInit {
+
+ constructor(private gamesService: GamesService) { }
+
+ /**
+ * The summaries for the supported games. Shows the entries under the "Games" menu.
+ */
+ summaries: ISummary[];
+
+ ngOnInit(): void {
+ this.gamesService.getSummaries().subscribe(
+ res => this.summaries = res,
+ err => console.error(err)
+ )
+ }
+
+}
diff --git a/src/assets/img/background.png b/src/assets/img/background.png
new file mode 100644
index 0000000..8fb95d7
Binary files /dev/null and b/src/assets/img/background.png differ
diff --git a/src/assets/img/logo.png b/src/assets/img/logo.png
new file mode 100644
index 0000000..3937827
Binary files /dev/null and b/src/assets/img/logo.png differ
diff --git a/src/assets/img/logos/mm.png b/src/assets/img/logos/mm.png
new file mode 100644
index 0000000..f5aaa18
Binary files /dev/null and b/src/assets/img/logos/mm.png differ
diff --git a/src/assets/img/logos/oot.png b/src/assets/img/logos/oot.png
new file mode 100644
index 0000000..fc201d9
Binary files /dev/null and b/src/assets/img/logos/oot.png differ
diff --git a/src/assets/img/logos/tmc.png b/src/assets/img/logos/tmc.png
new file mode 100644
index 0000000..0235464
Binary files /dev/null and b/src/assets/img/logos/tmc.png differ
diff --git a/src/assets/json/resources.json b/src/assets/json/resources.json
new file mode 100644
index 0000000..fb256b0
--- /dev/null
+++ b/src/assets/json/resources.json
@@ -0,0 +1,93 @@
+[
+ {
+ "header": "Decompilation Tools",
+ "subheader": "Decompilers",
+ "text": "mips_to_c",
+ "link": "https://github.com/matt-kempster/mips_to_c",
+ "slugs": ["oot", "mm"]
+ },
+ {
+ "header": "Decompilation Tools",
+ "subheader": "Decompilers",
+ "text": "mips_to_c (Online)",
+ "link": "https://simonsoftware.se/other/mips_to_c.py",
+ "slugs": ["oot", "mm"]
+ },
+ {
+ "header": "Decompilation Tools",
+ "subheader": "Decompilers",
+ "text": "Ghidra",
+ "link": "https://github.com/NationalSecurityAgency/ghidra",
+ "slugs": ["oot", "mm", "tmc"]
+ },
+ {
+ "header": "Decompilation Tools",
+ "subheader": "Decompilers",
+ "text": "Zelda64Loader",
+ "link": "https://github.com/Random06457/Zelda64Loader",
+ "description": "Loader for z64 ROM files",
+ "slugs": ["oot", "mm"]
+ },
+ {
+ "header": "Decompilation Documentation & Guides",
+ "text": "IDO O2 Decompilation Notes (IDO 5.3 & 7.1)",
+ "link": "https://hackmd.io/vPmcgdaFSlq4R2mfkq4bJg",
+ "slugs": ["oot", "mm"]
+ },
+ {
+ "header": "Decompilation Documentation & Guides",
+ "text": "N64 GFX Decompilation Guide (F3DEX & S2DEX)",
+ "link": "https://hackmd.io/m3E7g2YaSxiwUv9QKBLYyA",
+ "slugs": ["oot", "mm"]
+ },
+ {
+ "header": "Decompilation Documentation & Guides",
+ "text": "MIPS Calling Conventions",
+ "link": "https://courses.cs.washington.edu/courses/cse410/09sp/examples/MIPSCallingConventionsSummary.pdf",
+ "slugs": ["oot", "mm"]
+ },
+ {
+ "header": "Decompilation Documentation & Guides",
+ "text": "MIPS Instruction Set",
+ "link": "https://www.cs.cmu.edu/afs/cs/academic/class/15740-f97/public/doc/mips-isa.pdf",
+ "slugs": ["oot", "mm"]
+ },
+ {
+ "header": "Zelda Documentation",
+ "text": "TCRF #Zelda Sheets",
+ "link": "https://drive.google.com/drive/folders/0B2P7Sh4YYPZuSVRLV1pLSTRkbVk",
+ "slugs": ["oot", "mm"]
+ },
+ {
+ "header": "Zelda Documentation",
+ "text": "TMC Research",
+ "link": "https://drive.google.com/drive/folders/1hvEhcaUQvcJ1vUw5QWFTk7_La_ELKD87",
+ "slugs": ["tmc"]
+ },
+ {
+ "header": "Zelda Documentation",
+ "subheader": "CloudModding Wiki",
+ "text": "CloudModding (OOT)",
+ "link": "https://wiki.cloudmodding.com/oot/Main_Page",
+ "slugs": ["oot"]
+ },
+ {
+ "header": "Zelda Documentation",
+ "subheader": "CloudModding Wiki",
+ "text": "CloudModding (MM)",
+ "link": "https://wiki.cloudmodding.com/mm/Main_Page",
+ "slugs": ["mm"]
+ },
+ {
+ "header": "Project Resources",
+ "text": "Project Sheets",
+ "link": "https://drive.google.com/drive/folders/1ONmR0gzG3xCUufaNF6ATL2u5p90Cg6Kq",
+ "slugs": ["oot", "mm", "tmc"]
+ },
+ {
+ "header": "Project Resources",
+ "text": "OoT Debug Source Code Structure",
+ "link": "https://docs.google.com/spreadsheets/d/1hiP0JH_rEcq-s8vOnnoNPy2ooXhxl8qas4HItn30-Oc",
+ "slugs": ["oot"]
+ }
+]
diff --git a/src/assets/json/summaries.json b/src/assets/json/summaries.json
new file mode 100644
index 0000000..5115ebf
--- /dev/null
+++ b/src/assets/json/summaries.json
@@ -0,0 +1,28 @@
+[
+ {
+ "slug": "oot",
+ "title": "Ocarina of Time",
+ "progress": 0.99,
+ "links": {
+ "github": "https://www.github.com/zeldaret/oot",
+ "trello": "https://trello.com/b/qUqnUkWY/ocarina-of-time-decompilation"
+ }
+ },
+ {
+ "slug": "mm",
+ "title": "Majora's Mask",
+ "progress": 0,
+ "links": {
+ "github": "https://www.github.com/zeldaret/mm",
+ "trello": "https://trello.com/b/ruxw9n6m/majoras-mask-decompilation"
+ }
+ },
+ {
+ "slug": "tmc",
+ "title": "The Minish Cap",
+ "progress": 0,
+ "links": {
+ "github": "https://www.github.com/zeldaret/tmc"
+ }
+ }
+]
diff --git a/src/index.html b/src/index.html
index 305dcbe..7fd479f 100644
--- a/src/index.html
+++ b/src/index.html
@@ -6,6 +6,9 @@
+
+
+
diff --git a/src/styles.scss b/src/styles.scss
index 90d4ee0..ce7c6a0 100644
--- a/src/styles.scss
+++ b/src/styles.scss
@@ -1 +1,9 @@
/* You can add global styles to this file, and also import other style files */
+
+html, body { height: 100%; }
+body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }
+
+fa-icon {
+ font-size: 28px;
+ vertical-align: unset;
+}
diff --git a/tsconfig.json b/tsconfig.json
index 6df8283..5107c95 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -5,7 +5,8 @@
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"forceConsistentCasingInFileNames": true,
- "strict": true,
+ "strict": false,
+ "strictPropertyInitialization": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"sourceMap": true,