diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..dd86fd9
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,14 @@
+# Ignore the node_modules directory
+node_modules
+
+# Ignore the dist directory
+dist
+
+# Ignore the .git directory
+.git
+
+# Ignore the .gitignore file
+.gitignore
+
+# Ignore the .dockerignore file
+.dockerignore
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..7924f62
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,11 @@
+#stage 1
+FROM node:18-slim as node
+WORKDIR /app
+COPY . .
+RUN npm install
+RUN npm run build:ssr --omit=dev
+#stage 2
+FROM node:18-slim
+COPY --from=node /app/dist /app/dist
+WORKDIR /app
+CMD ["node", "dist/Portfolio/server/main.js"]
diff --git a/Inspiration/Github style.jpg b/Inspiration/Github style.jpg
deleted file mode 100644
index 77e0e8c..0000000
Binary files a/Inspiration/Github style.jpg and /dev/null differ
diff --git a/Inspiration/pie chart.png b/Inspiration/pie chart.png
deleted file mode 100644
index 5030459..0000000
Binary files a/Inspiration/pie chart.png and /dev/null differ
diff --git a/angular.json b/angular.json
index 1dbfd13..ef45ca8 100644
--- a/angular.json
+++ b/angular.json
@@ -27,7 +27,9 @@
"inlineStyleLanguage": "scss",
"assets": [
"src/favicon.ico",
- "src/assets"
+ "src/assets",
+ "src/robots.txt",
+ "src/sitemap.xml"
],
"styles": [
"src/styles.scss",
@@ -40,13 +42,13 @@
"budgets": [
{
"type": "initial",
- "maximumWarning": "500kb",
- "maximumError": "1mb"
+ "maximumWarning": "5mb",
+ "maximumError": "10mb"
},
{
"type": "anyComponentStyle",
- "maximumWarning": "2kb",
- "maximumError": "4kb"
+ "maximumWarning": "100kb",
+ "maximumError": "100kb"
}
],
"outputHashing": "all"
diff --git a/package-lock.json b/package-lock.json
index cf063d1..d4dc4d4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -20,13 +20,10 @@
"@angular/platform-server": "^15.1.0",
"@angular/router": "^15.1.0",
"@nguniversal/express-engine": "^15.1.0",
- "@sweetalert2/theme-dark": "^5.0.15",
"chart.js": "^4.2.1",
"express": "^4.15.2",
- "ngx-device-detector": "^5.0.1",
"pocketbase": "^0.11.0",
"rxjs": "~7.8.0",
- "sweetalert2": "^11.7.2",
"tslib": "^2.3.0",
"zone.js": "~0.12.0"
},
@@ -3933,11 +3930,6 @@
"integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==",
"dev": true
},
- "node_modules/@sweetalert2/theme-dark": {
- "version": "5.0.15",
- "resolved": "https://registry.npmjs.org/@sweetalert2/theme-dark/-/theme-dark-5.0.15.tgz",
- "integrity": "sha512-g1QCwQVOkiAz5hIEBOIvvu0580lubu4KuQlod+48QetYzGIEXNlHEH36QihCDnGVgE6vx48iO48w9q0WrZWyHQ=="
- },
"node_modules/@tootallnate/once": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
@@ -10036,18 +10028,6 @@
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true
},
- "node_modules/ngx-device-detector": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ngx-device-detector/-/ngx-device-detector-5.0.1.tgz",
- "integrity": "sha512-hVKaGzyXzy6zeliYyN7runz3eOOsh3tmZ8A6P5MSpHIjVjSx3pUJcobFTKNyHGn/zGS4JFWuhSSb7QmNwmqK9w==",
- "dependencies": {
- "tslib": "^2.0.0"
- },
- "peerDependencies": {
- "@angular/common": "^15.0.0",
- "@angular/core": "^15.0.0"
- }
- },
"node_modules/nice-napi": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz",
@@ -12453,15 +12433,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/sweetalert2": {
- "version": "11.7.2",
- "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.7.2.tgz",
- "integrity": "sha512-atPjDa3fv/4xwZpiAt7FZUgAhR5VAASiLP2hu7HUeVDXx+v4/9nD1W0u8xal1e9f2/qGh0DwTxPXPV9XoZIBvg==",
- "funding": {
- "type": "individual",
- "url": "https://github.com/sponsors/limonte"
- }
- },
"node_modules/symbol-observable": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz",
@@ -16562,11 +16533,6 @@
"integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==",
"dev": true
},
- "@sweetalert2/theme-dark": {
- "version": "5.0.15",
- "resolved": "https://registry.npmjs.org/@sweetalert2/theme-dark/-/theme-dark-5.0.15.tgz",
- "integrity": "sha512-g1QCwQVOkiAz5hIEBOIvvu0580lubu4KuQlod+48QetYzGIEXNlHEH36QihCDnGVgE6vx48iO48w9q0WrZWyHQ=="
- },
"@tootallnate/once": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
@@ -21312,14 +21278,6 @@
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true
},
- "ngx-device-detector": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ngx-device-detector/-/ngx-device-detector-5.0.1.tgz",
- "integrity": "sha512-hVKaGzyXzy6zeliYyN7runz3eOOsh3tmZ8A6P5MSpHIjVjSx3pUJcobFTKNyHGn/zGS4JFWuhSSb7QmNwmqK9w==",
- "requires": {
- "tslib": "^2.0.0"
- }
- },
"nice-napi": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz",
@@ -23136,11 +23094,6 @@
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"dev": true
},
- "sweetalert2": {
- "version": "11.7.2",
- "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.7.2.tgz",
- "integrity": "sha512-atPjDa3fv/4xwZpiAt7FZUgAhR5VAASiLP2hu7HUeVDXx+v4/9nD1W0u8xal1e9f2/qGh0DwTxPXPV9XoZIBvg=="
- },
"symbol-observable": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz",
diff --git a/package.json b/package.json
index f8ec32e..7fe9fea 100644
--- a/package.json
+++ b/package.json
@@ -26,13 +26,10 @@
"@angular/platform-server": "^15.1.0",
"@angular/router": "^15.1.0",
"@nguniversal/express-engine": "^15.1.0",
- "@sweetalert2/theme-dark": "^5.0.15",
"chart.js": "^4.2.1",
"express": "^4.15.2",
- "ngx-device-detector": "^5.0.1",
"pocketbase": "^0.11.0",
"rxjs": "~7.8.0",
- "sweetalert2": "^11.7.2",
"tslib": "^2.3.0",
"zone.js": "~0.12.0"
},
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index d3d64a8..061c24f 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -1,4 +1,5 @@
import { Component } from '@angular/core';
+import {SeoService} from "./services/seo.service";
@Component({
selector: 'app-root',
@@ -7,4 +8,6 @@ import { Component } from '@angular/core';
})
export class AppComponent {
+ public constructor(private seo: SeoService) {}
+
}
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 8fb016a..633258f 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -22,6 +22,11 @@ import { ContactComponent } from './sites/contact/contact.component';
import { AboutComponent } from './sites/about/about.component';
import {MatInputModule} from "@angular/material/input";
import {ReactiveFormsModule} from "@angular/forms";
+import {MatSnackBarModule} from "@angular/material/snack-bar";
+import { TimestampComponent } from './components/timestamp/timestamp.component';
+import { CarrierPipe } from './pipes/carrier.pipe';
+import { ExperiencePipe } from './pipes/experience.pipe';
+import { FooterComponent } from './components/footer/footer.component';
@NgModule({
declarations: [
@@ -38,7 +43,11 @@ import {ReactiveFormsModule} from "@angular/forms";
FrameworksPipe,
SkillsPipe,
ContactComponent,
- AboutComponent
+ AboutComponent,
+ TimestampComponent,
+ CarrierPipe,
+ ExperiencePipe,
+ FooterComponent
],
imports: [
BrowserModule.withServerTransition({appId: 'serverApp'}),
@@ -48,7 +57,8 @@ import {ReactiveFormsModule} from "@angular/forms";
MatButtonModule,
MatTooltipModule,
MatInputModule,
- ReactiveFormsModule
+ ReactiveFormsModule,
+ MatSnackBarModule
],
providers: [],
bootstrap: [AppComponent]
diff --git a/src/app/components/fancy-button/fancy-button.component.scss b/src/app/components/fancy-button/fancy-button.component.scss
index 37b00b6..1109770 100644
--- a/src/app/components/fancy-button/fancy-button.component.scss
+++ b/src/app/components/fancy-button/fancy-button.component.scss
@@ -1,4 +1,14 @@
-$text-move: hover-text-move 300ms forwards ease-out;
+@keyframes hover-text {
+ from {
+ opacity: var(--opa-1);
+ transform: translateY(0);
+ }
+
+ to {
+ opacity: var(--opa-2);
+ transform: translateY(-20px);
+ }
+}
.button {
display: flex;
@@ -13,42 +23,27 @@ $text-move: hover-text-move 300ms forwards ease-out;
border-radius: 20px;
text-decoration: none;
font-size: 13px;
+ overflow: hidden;
.text-1 {
+ --opa-2: 0;
+ --opa-1: 1;
margin-top: 12px;
}
.text-2 {
+ --opa-2: 1;
+ --opa-1: 0;
opacity: 0;
}
&:hover {
.text-1 {
- animation: hover-text 250ms forwards ease-out reverse, $text-move;
+ animation: hover-text 300ms forwards ease-out;
}
.text-2 {
- animation: hover-text 300ms forwards ease-out, $text-move;
+ animation: hover-text 300ms forwards ease-out;
}
}
}
-
-@keyframes hover-text {
- from {
- opacity: 0;
- }
-
- to {
- opacity: 1;
- }
-}
-
-@keyframes hover-text-move {
- from {
- transform: translateY(0);
- }
-
- to {
- transform: translateY(-20px);
- }
-}
diff --git a/src/app/components/footer/footer.component.html b/src/app/components/footer/footer.component.html
new file mode 100644
index 0000000..e7382b1
--- /dev/null
+++ b/src/app/components/footer/footer.component.html
@@ -0,0 +1,11 @@
+
diff --git a/src/app/components/footer/footer.component.scss b/src/app/components/footer/footer.component.scss
new file mode 100644
index 0000000..58e44ae
--- /dev/null
+++ b/src/app/components/footer/footer.component.scss
@@ -0,0 +1,39 @@
+@use "src/theme";
+
+#footer {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 30px;
+ margin-top: 120px;
+ user-select: unset;
+
+ .footer-title {
+ background: theme.$gradient;
+ background-clip: text;
+ color: transparent;
+ font-weight: bold;
+ user-select: none;
+ }
+
+ a {
+ text-decoration: none;
+ }
+
+ #social-media {
+ display: flex;
+ gap: 5px;
+ user-select: none;
+
+ .header-social > img {
+ width: 25px;
+ height: 25px;
+ }
+ }
+}
+
+@media screen and (max-width: theme.$mobile-width) {
+ #footer *:not(.footer-title) {
+ display: none;
+ }
+}
diff --git a/src/app/components/footer/footer.component.ts b/src/app/components/footer/footer.component.ts
new file mode 100644
index 0000000..fd0e3eb
--- /dev/null
+++ b/src/app/components/footer/footer.component.ts
@@ -0,0 +1,20 @@
+import {Component, OnInit} from '@angular/core';
+import {BackendService} from "../../services/backend.service";
+import {Social} from "../../models/social";
+
+@Component({
+ selector: 'app-footer',
+ templateUrl: './footer.component.html',
+ styleUrls: ['./footer.component.scss']
+})
+export class FooterComponent implements OnInit {
+
+ public socialLinks: Social[];
+
+ public constructor(private backend: BackendService) {}
+
+ async ngOnInit() {
+ this.socialLinks = await this.backend.getSocials();
+ }
+
+}
diff --git a/src/app/components/navigation/navigation.component.html b/src/app/components/navigation/navigation.component.html
index 24728b4..b321e1a 100644
--- a/src/app/components/navigation/navigation.component.html
+++ b/src/app/components/navigation/navigation.component.html
@@ -2,22 +2,22 @@
Leon Hoppe
-