From 9614081c6bbe6ecf0979717b7ab21800acc7ae2a Mon Sep 17 00:00:00 2001 From: axherrm Date: Sat, 20 Jan 2024 20:26:42 +0100 Subject: [PATCH 1/2] add navbar in sidebar --- src/app/app.component.html | 85 ++++++++++--------- src/app/app.component.scss | 11 ++- src/app/app.component.ts | 22 +++-- .../navbar-dot/navbar-dot.component.html | 1 + .../navbar-dot/navbar-dot.component.scss | 74 ++++++++++++++++ .../navbar-dot/navbar-dot.component.ts | 56 ++++++++++++ src/app/data/data.service.ts | 2 +- src/app/data/model.ts | 46 +++++++++- .../sections/sidebar/sidebar.component.html | 15 ++++ .../sidebar/sidebar.component.scss} | 42 +++++++-- src/app/sections/sidebar/sidebar.component.ts | 75 ++++++++++++++++ src/data/general.json | 8 +- src/styles.scss | 27 ++++++ 13 files changed, 401 insertions(+), 63 deletions(-) create mode 100644 src/app/components/navbar-dot/navbar-dot.component.html create mode 100644 src/app/components/navbar-dot/navbar-dot.component.scss create mode 100644 src/app/components/navbar-dot/navbar-dot.component.ts create mode 100644 src/app/sections/sidebar/sidebar.component.html rename src/app/{style/language-selector.scss => sections/sidebar/sidebar.component.scss} (58%) create mode 100644 src/app/sections/sidebar/sidebar.component.ts diff --git a/src/app/app.component.html b/src/app/app.component.html index 9095485..54da8c1 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -19,54 +19,55 @@
+ -
- +
+
+

+

+
-
-
-
-

-

-
-
+
+
+ + + + + + + + + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - - - - - - - + +
+ +
- -
- -
+ -
+
+
diff --git a/src/app/app.component.scss b/src/app/app.component.scss index 4424760..e4db8dd 100644 --- a/src/app/app.component.scss +++ b/src/app/app.component.scss @@ -1,4 +1,3 @@ -@use 'style/language-selector'; @use 'style/lenis'; #background-img { @@ -60,6 +59,16 @@ flex-direction: column; text-align: center; align-items: center; + + // for rotation + position: relative; + transform-style: preserve-3d; + transform-origin: left; + transition: all .6s ease-in-out; + &.rotated { + transition: all .4s ease-in-out; + transform: rotateY(calc(1 * var(--rotate-angle))); + } } :host ::ng-deep { diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 5f62c0e..cf1a109 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -11,6 +11,8 @@ import 'js-circle-progress'; import {TimelineCardComponent} from "./components/timeline-card/timeline-card.component"; import {HeadingCardComponent} from "./components/heading-card/heading-card.component"; import {SkillsCardComponent} from "./components/skills-card/skills-card.component"; +import {NavbarDotComponent} from "./components/navbar-dot/navbar-dot.component"; +import {SidebarComponent} from "./sections/sidebar/sidebar.component"; gsap.registerPlugin(ScrollTrigger); @@ -29,7 +31,10 @@ import "./js/lenis.js"; // Custom TimelineCardComponent, HeadingCardComponent, - SkillsCardComponent], + SkillsCardComponent, + NavbarDotComponent, + SidebarComponent + ], templateUrl: './app.component.html', styleUrl: './app.component.scss' }) @@ -38,8 +43,9 @@ export class AppComponent { @ViewChild("backgroundImg", {read: ElementRef}) backgroundImage: ElementRef; @ViewChild("outest_container", {read: ElementRef}) outestContainer: ElementRef; @ViewChild("progress_bar", {read: ElementRef}) progressBar: ElementRef; + // @ViewChild("content_container", {read: ElementRef}) contentContainer: ElementRef; @ViewChild("education_card", {read: ElementRef}) educationCard: ElementRef; - @ViewChild("lang_selector", {read: ElementRef}) langSelector: ElementRef; + @ViewChild("sidebar", {read: ElementRef}) sidebar: ElementRef; dataService: DataService; @@ -51,7 +57,7 @@ export class AppComponent { } ngAfterViewInit(): void { - this.addLangButtonAnimation(); + this.addSidebarAnimation(); this.addProgressBarAnimation(); // @ts-ignore for (let el of document.getElementsByClassName("alarm-clock-animated")) { @@ -86,7 +92,7 @@ export class AppComponent { }) } - addLangButtonAnimation() { + addSidebarAnimation() { const tl: gsap.core.Timeline = gsap.timeline({ scrollTrigger: { start: "top 90%", @@ -99,12 +105,12 @@ export class AppComponent { .to(this.backgroundImage.nativeElement, { filter: "blur(2px)" }) - .fromTo(this.langSelector.nativeElement, { + .fromTo(this.sidebar.nativeElement, { opacity: 0, ease: "power1.in", display: "none" }, { - display: "var(--display-side-elements)", + display: "var(--display-side-elements-flex)", opacity: 1, }, "<"); } @@ -115,7 +121,7 @@ export class AppComponent { start: "top bottom", trigger: el, end: "center center", - // markers: true, // TODO remove + // markers: true, scrub: true, }, defaults: { @@ -127,7 +133,7 @@ export class AppComponent { start: "center center", trigger: el, end: "bottom top", - // markers: true, // TODO remove + // markers: true, scrub: true, }, defaults: { diff --git a/src/app/components/navbar-dot/navbar-dot.component.html b/src/app/components/navbar-dot/navbar-dot.component.html new file mode 100644 index 0000000..b557c35 --- /dev/null +++ b/src/app/components/navbar-dot/navbar-dot.component.html @@ -0,0 +1 @@ + diff --git a/src/app/components/navbar-dot/navbar-dot.component.scss b/src/app/components/navbar-dot/navbar-dot.component.scss new file mode 100644 index 0000000..8607dfa --- /dev/null +++ b/src/app/components/navbar-dot/navbar-dot.component.scss @@ -0,0 +1,74 @@ +:host { + height: 1.2rem; + width: 1.2rem; + border-radius: 50%; + border: 3px solid rgb(255, 255, 255); + transition: all .6s ease-in-out; + user-select: none; + + display: flex; + align-items: center; + justify-content: center; + font-size: 0; + + &:not(.extended):hover { + transition: all .1s ease-in-out; + height: 1.5rem; + width: 1.5rem; + border: 5px solid rgb(255, 255, 255); + background: rgba(0, 0, 0, 0.3); + cursor: pointer; + } + + &.active:not(.extended) { + background: #e134e8 !important; + } + &.extended { + position: relative; + transition: all .4s ease-in-out; + width: 20rem; + height: 5rem; + margin: 0 3rem; + border-radius: 20px; + //transform: translateX(-4rem); + font-size: 1.3rem; + &.active { + border: 0; + &::before { + // code from: https://dev.to/afif/border-with-gradient-and-radius-387f + content: ""; + position: absolute; + inset: 0; + border-radius: 20px; + padding: 5px; + margin: -5px; + background: linear-gradient(to right, #a445b2, #fa4299); + -webkit-mask: + linear-gradient(#fff 0 0) content-box, + linear-gradient(#fff 0 0); + -webkit-mask-composite: xor; + mask-composite: exclude; + } + } + &:hover { + width: 21rem; + margin: 0 1rem; + height: 5.3rem; + font-size: 1.4rem; + background: rgba(0, 0, 0, 0.3); + cursor: pointer; + } + span { + display: block; + } + } +} + +span { + display: none; + transition: all .4s ease-in-out; + color: white; + text-align: center; + margin-top: auto; + margin-bottom: auto; +} diff --git a/src/app/components/navbar-dot/navbar-dot.component.ts b/src/app/components/navbar-dot/navbar-dot.component.ts new file mode 100644 index 0000000..279def2 --- /dev/null +++ b/src/app/components/navbar-dot/navbar-dot.component.ts @@ -0,0 +1,56 @@ +import {Component, EventEmitter, HostBinding, HostListener, Input, Output} from '@angular/core'; +import {Section} from "../../data/model"; +import gsap from "gsap"; +import {ScrollToPlugin} from "gsap/ScrollToPlugin"; + +gsap.registerPlugin(ScrollToPlugin) + +@Component({ + selector: 'navbar-dot', + standalone: true, + imports: [], + templateUrl: './navbar-dot.component.html', + styleUrl: './navbar-dot.component.scss' +}) +export class NavbarDotComponent { + + @Input({required: true}) section: Section; + + @HostBinding('class.extended') + @Input() + extended = false; + + @HostBinding('class.active') + @Input() + active = false; + + @Output() hover: EventEmitter = new EventEmitter(); + + delayTimeout: any; + + @HostListener("mouseenter") + onMouseover() { + this.delayTimeout = setTimeout(() => this.hover.emit(), 300); + } + + /** + * In case the hovering stops before delay timeout is over, the hovering is not forwarded. + */ + @HostListener("mouseleave") + onMouseleave() { + clearTimeout(this.delayTimeout); + } + + @HostListener("click") + onClick() { + gsap.to(window, { + duration: 2, + scrollTo: { + y: `#${this.section.id}`, + offsetY: 20, + }, + ease: "power1.inOut" + }); + } + +} diff --git a/src/app/data/data.service.ts b/src/app/data/data.service.ts index b3c8887..d30aa0c 100644 --- a/src/app/data/data.service.ts +++ b/src/app/data/data.service.ts @@ -33,7 +33,7 @@ export class DataService { // @ts-ignore this.education = educationJson[this.lang]; // @ts-ignore - this.languagePack = generalJson[this.lang]; + this.languagePack = new LanguagePack(generalJson[this.lang]); // @ts-ignore this.experience = experienceJson[this.lang]; // @ts-ignore diff --git a/src/app/data/model.ts b/src/app/data/model.ts index 32f98be..b3fe537 100644 --- a/src/app/data/model.ts +++ b/src/app/data/model.ts @@ -33,7 +33,7 @@ export interface SkillCategory { skills: Skill[]; } -export interface LanguagePack { +export interface ILanguagePack { /** * This is the identifier which is used in the languages array and for definition of the object. */ @@ -46,7 +46,51 @@ export interface LanguagePack { isoAlpha2: string; heading: string; subheading: string; + home: string; education: string; experience: string; skills: string; + about: string; +} + +export class LanguagePack implements ILanguagePack { + id: string; + name: string; + isoAlpha2: string; + heading: string; + subheading: string; + home: string; + education: string; + experience: string; + skills: string; + about: string; + + sections: Section[]; + + constructor(langPack: ILanguagePack) { + this.id = langPack.id; + this.name = langPack.name; + this.isoAlpha2 = langPack.isoAlpha2; + this.heading = langPack.heading; + this.subheading = langPack.subheading; + this.home = langPack.home; + this.education = langPack.education; + this.experience = langPack.experience; + this.skills = langPack.skills; + this.about = langPack.about; + + this.sections = [ + {name: this.home, id: "home-start", position: 0}, + {name: this.education, id: "education-start", position: 1}, + {name: this.experience, id: "experience-start", position: 2}, + {name: this.skills, id: "skills-start", position: 3}, + {name: this.about, id: "about-start", position: 4}, + ]; + } +} + +export interface Section { + name: string; + id: string; + position: number; } diff --git a/src/app/sections/sidebar/sidebar.component.html b/src/app/sections/sidebar/sidebar.component.html new file mode 100644 index 0000000..fb2cf3f --- /dev/null +++ b/src/app/sections/sidebar/sidebar.component.html @@ -0,0 +1,15 @@ + + + diff --git a/src/app/style/language-selector.scss b/src/app/sections/sidebar/sidebar.component.scss similarity index 58% rename from src/app/style/language-selector.scss rename to src/app/sections/sidebar/sidebar.component.scss index 0e6a27d..8966bc7 100644 --- a/src/app/style/language-selector.scss +++ b/src/app/sections/sidebar/sidebar.component.scss @@ -1,3 +1,37 @@ +.sidebar-container { + position: fixed; + width: auto; + min-width: 5vw; + max-width: 30vw; + top: 0; + right: 0; + z-index: 1000; + + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; +} + +#lang-selector { + position: absolute; + top: 2vh; + transform: translateX(-32px); + //align-self: flex-end; +} + +#navbar { + //position: absolute; + height: 100vh; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 0.5rem; + //flex-grow: 1; +} + +// Lang Button :host ::ng-deep { .flag-icon { width: 100%; @@ -31,11 +65,3 @@ } } } - -#lang-selector { - position: fixed; - right: calc(2vh + 64px); - top: 2vh; - z-index: 1000; - display: var(--display-side-elements); -} diff --git a/src/app/sections/sidebar/sidebar.component.ts b/src/app/sections/sidebar/sidebar.component.ts new file mode 100644 index 0000000..a0fead7 --- /dev/null +++ b/src/app/sections/sidebar/sidebar.component.ts @@ -0,0 +1,75 @@ +import { + ChangeDetectorRef, + Component, + HostListener, + Input, + Output, + QueryList, + ViewChildren +} from '@angular/core'; +import {NavbarDotComponent} from "../../components/navbar-dot/navbar-dot.component"; +import {NgForOf} from "@angular/common"; +import {SpeedDialModule} from "primeng/speeddial"; +import {Section} from "../../data/model"; +import {MenuItem} from "primeng/api"; +import {ScrollTrigger} from "gsap/ScrollTrigger"; + +@Component({ + selector: 'sidebar', + standalone: true, + imports: [ + NavbarDotComponent, + NgForOf, + SpeedDialModule + ], + templateUrl: './sidebar.component.html', + styleUrl: './sidebar.component.scss' +}) +export class SidebarComponent { + + @Input({required: true}) sections: Section[]; + @Input({required: true}) languagesMenuItems: MenuItem[]; + + @Output() hovered: boolean = false; + + @ViewChildren("navbar_dot", {read: NavbarDotComponent}) dots: QueryList; + + sectionActive = 0; + + changeDetectorRef: ChangeDetectorRef; + + constructor(changeDetectorRef: ChangeDetectorRef) { + this.changeDetectorRef = changeDetectorRef; + } + + ngAfterViewInit(): void { + this.addSelectedAnimation(); + } + + @HostListener("mouseleave") + onMouseleave() { + this.hovered = false; + } + + addSelectedAnimation() { + for (let i = 0; i < this.sections.length; i++) { + const section = this.sections[i]; + ScrollTrigger.create({ + start: "top 50%", + trigger: `#${section.id}`, + end: "+=1", + // markers: true, + onEnter: self => { + console.log("Active section is ", i, "onEnter"); + this.sectionActive = i; + this.changeDetectorRef.detectChanges(); + }, + onEnterBack: self => { + console.log("Active section is ", i-1, "onEnterBack"); + this.sectionActive = i-1; + this.changeDetectorRef.detectChanges(); + } + }); + } + } +} diff --git a/src/data/general.json b/src/data/general.json index 00e2efa..fdd23b0 100644 --- a/src/data/general.json +++ b/src/data/general.json @@ -7,9 +7,11 @@ "isoAlpha2": "de", "heading": "Axel Herrmann", "subheading": "Software Engineer", + "home": "Home", "education": "Bildungsweg", "experience": "Erfahrung", - "skills": "Kenntnisse" + "skills": "Kenntnisse", + "about": "Über" }, "en": { "id": "en", @@ -17,8 +19,10 @@ "isoAlpha2": "us", "heading": "Axel Herrmann", "subheading": "Software Engineer", + "home": "Home", "education": "Education", "experience": "Experience", - "skills": "Skills" + "skills": "Skills", + "about": "About" } } diff --git a/src/styles.scss b/src/styles.scss index e8a9b82..4afaf50 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -45,10 +45,37 @@ html { @media (max-width: 1000px) { --main-content-width: 100vw; --display-side-elements: none; + --display-side-elements-flex: none; } @media (min-width: 1000px) { --main-content-width: max(60vw, 1000px); --display-side-elements: block; + --display-side-elements-flex: flex; } --main-content-side-margin: calc((100vw - var(--main-content-width)) / 2); + + /******************************************************* + * Rotation calculations * + *******************************************************/ + /* Input */ + --side-content-width-visible-percent: calc(4/5); + + /* Calculations */ + --rotated-depth-percent: calc(sqrt(1 - pow(var(--side-content-width-visible-percent),2))); + /* alpha = acos(ancathete / hypotenuse) */ + --rotate-angle: calc(acos(var(--side-content-width-visible-percent))); + + /* + (180 - alpha) / 2 + => result has to be subtracted from 180 again because of css rotation rules + */ + --side-content-rotation: calc(180deg - (180deg - var(--rotate-angle)) / 2); + /* + a² = b² + c² - 2bc * cos(alpha) + a = √(b² + c² - 2bc * cos(alpha)) + a = √(1² + 1² - 2 * cos(alpha)) + a = √(2 - 2cos(alpha)) + */ + /*--side-content-length-percent: calc(2 - 2 * sqrt(cos(var(--rotate-angle))));*/ + --side-content-length-percent: sqrt(2 - 2 * cos(var(--rotate-angle))); } From 09978f3ff813bf26af24a86d92da5c1b988a949d Mon Sep 17 00:00:00 2001 From: axherrm Date: Mon, 22 Jan 2024 10:30:21 +0100 Subject: [PATCH 2/2] remove extendable sidebar (delay to 2.0) --- src/app/app.component.html | 3 ++- src/app/app.component.scss | 5 +++- .../navbar-dot/navbar-dot.component.ts | 24 +++++++++---------- .../sections/sidebar/sidebar.component.html | 4 ++-- .../sections/sidebar/sidebar.component.scss | 6 ++++- src/app/sections/sidebar/sidebar.component.ts | 10 ++++---- 6 files changed, 30 insertions(+), 22 deletions(-) diff --git a/src/app/app.component.html b/src/app/app.component.html index 54da8c1..5ddda08 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -29,7 +29,8 @@

-
+ +
diff --git a/src/app/app.component.scss b/src/app/app.component.scss index e4db8dd..75dfcc2 100644 --- a/src/app/app.component.scss +++ b/src/app/app.component.scss @@ -67,7 +67,10 @@ transition: all .6s ease-in-out; &.rotated { transition: all .4s ease-in-out; - transform: rotateY(calc(1 * var(--rotate-angle))); + //transform: rotateY(calc(1 * var(--rotate-angle))); + //transform: rotateX(70deg); + //transform: translateY(-5000px); + //transform: scale(0.6); } } diff --git a/src/app/components/navbar-dot/navbar-dot.component.ts b/src/app/components/navbar-dot/navbar-dot.component.ts index 279def2..5316df8 100644 --- a/src/app/components/navbar-dot/navbar-dot.component.ts +++ b/src/app/components/navbar-dot/navbar-dot.component.ts @@ -16,30 +16,30 @@ export class NavbarDotComponent { @Input({required: true}) section: Section; - @HostBinding('class.extended') - @Input() - extended = false; + // @HostBinding('class.extended') + // @Input() + // extended = false; @HostBinding('class.active') @Input() active = false; - @Output() hover: EventEmitter = new EventEmitter(); + // @Output() hover: EventEmitter = new EventEmitter(); delayTimeout: any; - @HostListener("mouseenter") - onMouseover() { - this.delayTimeout = setTimeout(() => this.hover.emit(), 300); - } + // @HostListener("mouseenter") + // onMouseover() { + // this.delayTimeout = setTimeout(() => this.hover.emit(), 300); + // } /** * In case the hovering stops before delay timeout is over, the hovering is not forwarded. */ - @HostListener("mouseleave") - onMouseleave() { - clearTimeout(this.delayTimeout); - } + // @HostListener("mouseleave") + // onMouseleave() { + // clearTimeout(this.delayTimeout); + // } @HostListener("click") onClick() { diff --git a/src/app/sections/sidebar/sidebar.component.html b/src/app/sections/sidebar/sidebar.component.html index fb2cf3f..4f4fca8 100644 --- a/src/app/sections/sidebar/sidebar.component.html +++ b/src/app/sections/sidebar/sidebar.component.html @@ -1,7 +1,7 @@ diff --git a/src/app/sections/sidebar/sidebar.component.scss b/src/app/sections/sidebar/sidebar.component.scss index 8966bc7..c2a349b 100644 --- a/src/app/sections/sidebar/sidebar.component.scss +++ b/src/app/sections/sidebar/sidebar.component.scss @@ -1,3 +1,8 @@ +:host { + position: absolute; + z-index: 1000; +} + .sidebar-container { position: fixed; width: auto; @@ -5,7 +10,6 @@ max-width: 30vw; top: 0; right: 0; - z-index: 1000; display: flex; flex-direction: column; diff --git a/src/app/sections/sidebar/sidebar.component.ts b/src/app/sections/sidebar/sidebar.component.ts index a0fead7..6bcfcf7 100644 --- a/src/app/sections/sidebar/sidebar.component.ts +++ b/src/app/sections/sidebar/sidebar.component.ts @@ -30,7 +30,7 @@ export class SidebarComponent { @Input({required: true}) sections: Section[]; @Input({required: true}) languagesMenuItems: MenuItem[]; - @Output() hovered: boolean = false; + // @Output() hovered: boolean = false; @ViewChildren("navbar_dot", {read: NavbarDotComponent}) dots: QueryList; @@ -46,10 +46,10 @@ export class SidebarComponent { this.addSelectedAnimation(); } - @HostListener("mouseleave") - onMouseleave() { - this.hovered = false; - } + // @HostListener("mouseleave") + // onMouseleave() { + // this.hovered = false; + // } addSelectedAnimation() { for (let i = 0; i < this.sections.length; i++) {