diff --git a/src/app/pages/website/about-me/about-me.component.html b/src/app/pages/website/about-me/about-me.component.html new file mode 100644 index 00000000..0859a349 --- /dev/null +++ b/src/app/pages/website/about-me/about-me.component.html @@ -0,0 +1,41 @@ +
+

BIOGRAPHY

+

Who am I?

+
+
+
+ + + +
+
+ About me + Lucas Ho's Details + + I have over 2 years of experience providing excellent Full-Stack software development services. My expertise lies in designing, developing, and deploying diverse software, with a focus on Mobile and Web Applications, Database Management Systems, E-commerce Platforms, API Integrations, and Cloud-Based Solutions. + + +
+
+
\ No newline at end of file diff --git a/src/app/pages/website/about-me/about-me.component.scss b/src/app/pages/website/about-me/about-me.component.scss new file mode 100644 index 00000000..0998b560 --- /dev/null +++ b/src/app/pages/website/about-me/about-me.component.scss @@ -0,0 +1,110 @@ +.about-me-container{ + background-color: #000000; + width: 100%; + height: 1200px; + padding-top: 100px; +} + +.vertical-box{ + width: 6px; + height: 70px; + background-color: #dac5a7; + border-radius: 5px; +} + +.infro-container{ + width: 100%; + height: 100%; + margin-top: 100px; +} + +.img-box{ + width: 640px; + height: 760px; + display: inline-block; + white-space: nowrap; +} + +.group-img{ + z-index: 0; + margin-top: -20px; + vertical-align: top; +} + +.intro-me-img{ + z-index: 2; + height: 712px; + width: 540px; + margin-left: -110px; + box-shadow: white 0px 2px 10px 0px; +} + +.ellipse-img{ + z-index: 0; + vertical-align: bottom; + margin-bottom: -20px; + margin-left: -15px; +} + +.intro-box{ + color: white; + width: 50%; + height: 85%; +} + +.information-box{ + width: 100%; + height: 60%; +} + +.information-box-s{ + width: 100%; + height: 50%; +} + +.button { + display: flex; + text-decoration: none; + flex-direction: column; + justify-content: center; + padding-left: 20px; + width: 350px; + height: 120px; + border-radius: 5px; + color: #dac5a7; + font-size: 1rem; + letter-spacing: .15rem; + transition: all .5s; + position: relative; + overflow: hidden; + z-index: 1; + &:after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + background-color: #22211e; + border-radius: 5px; + z-index: -2; + } + &:before { + content: ''; + position: absolute; + bottom: 0; + left: 0; + width: 3px; + height: 100%; + background-color: #dac5a7; + transition: all .5s; + border-radius: 5px; + z-index: -1; + } + &:hover { + color: black; + &:before { + width: 100%; + } + } +} \ No newline at end of file diff --git a/src/app/pages/website/about-me/about-me.component.spec.ts b/src/app/pages/website/about-me/about-me.component.spec.ts new file mode 100644 index 00000000..4d39ce72 --- /dev/null +++ b/src/app/pages/website/about-me/about-me.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AboutMeComponent } from './about-me.component'; + +describe('AboutMeComponent', () => { + let component: AboutMeComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [AboutMeComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(AboutMeComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/website/about-me/about-me.component.ts b/src/app/pages/website/about-me/about-me.component.ts new file mode 100644 index 00000000..df1208c2 --- /dev/null +++ b/src/app/pages/website/about-me/about-me.component.ts @@ -0,0 +1,16 @@ +import { Component, ElementRef } from '@angular/core'; + +@Component({ + selector: 'app-about-me', + standalone: true, + imports: [], + templateUrl: './about-me.component.html', + styleUrl: './about-me.component.scss' +}) +export class AboutMeComponent { + constructor(private elementRef: ElementRef) {} + + scrollTo() { + this.elementRef.nativeElement.scrollIntoView({ behavior: 'smooth', block: 'start' }); + } +} diff --git a/src/app/pages/website/back-portfolio/back-portfolio.component.html b/src/app/pages/website/back-portfolio/back-portfolio.component.html new file mode 100644 index 00000000..f74e53e6 --- /dev/null +++ b/src/app/pages/website/back-portfolio/back-portfolio.component.html @@ -0,0 +1,48 @@ +
+
+
+ +
+
+

Backend & APIs

+

E-Commerce System

+

Java / Spring Framework / Oracle Database / MSSQL / Groovy / Maven / JMeter / docker / Jira / Azure / Google Cloud's Apigee API Management

+

+ Proficient in developing large-scale, cloud-based E-Commerce System Solutions using SAP Hybris and the Java Spring framework, resulting in improved system stability, reliability, and optimal user experience. +
Adept at evaluating system performance through JMeter testing
+
Utilized Git for code versioning and collaboration, fostering efficient project development, maintenance, and teamwork.
+
Created Docker images for Continuous Integration and Continuous Deployment (CI/CD) pipelines
+
Implemented Google Cloud's Apigee API Management as a middleware and proxy for RESTful APIs
+

+
+
+ +
+
+ +
+
+

Backend & APIs

+

Bank and Financial API

+

Java / Spring Boot Framework / Gradle / Postman / RSA

+

+
Developed a suite of RESTful APIs using the Spring Boot framework.
+
These APIs are responsible for critical financial operations, including secure authentication mechanisms like login, one-time password (OTP), and biometric authentication.
+
Enable account management functionalities such as creating accounts and changing passwords, fixed deposit, money transfer, foreign currency exchange, as well as facilitating the buying and selling of Exchange-Traded Funds (ETFs).
+
The APIs were built with a strong emphasis on security, scalability, and adherence to RESTful architecture principles.

+
+
+ +
+
+ +
+
+

Backend & APIs

+

ERP solution for Printing industry

+

Kotlin / Gradle / Oracle / SQL Script / ERP / Programmable Logic Controller (PLC)

+

For monitoring the productivity of a printing company by tracking the status of numerous printers and print jobs. + These AIPs help gather data on printer uptime, job completion rates, print volumes, and error rates.

+
+
+
\ No newline at end of file diff --git a/src/app/pages/website/back-portfolio/back-portfolio.component.scss b/src/app/pages/website/back-portfolio/back-portfolio.component.scss new file mode 100644 index 00000000..f741189d --- /dev/null +++ b/src/app/pages/website/back-portfolio/back-portfolio.component.scss @@ -0,0 +1,34 @@ +.galleria-container{ + width: 640px; + height: 640px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.portfolio-container{ + width: 100%; + height: 600px; + margin-bottom: 10rem; + align-items: center; + .detail-container{ + height: 100%; + width: 560px; + display: flex; + flex-direction: column; + justify-content: center; + + .portfolio-type{ + color: #dac5a7; + } + .portfolio-name{ + color: white; + font-size: 3rem; + font-weight: bold; + } + .description{ + color: #7f7466; + } + } +} diff --git a/src/app/pages/website/back-portfolio/back-portfolio.component.spec.ts b/src/app/pages/website/back-portfolio/back-portfolio.component.spec.ts new file mode 100644 index 00000000..c43f6a43 --- /dev/null +++ b/src/app/pages/website/back-portfolio/back-portfolio.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { BackPortfolioComponent } from './back-portfolio.component'; + +describe('BackPortfolioComponent', () => { + let component: BackPortfolioComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [BackPortfolioComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(BackPortfolioComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/website/back-portfolio/back-portfolio.component.ts b/src/app/pages/website/back-portfolio/back-portfolio.component.ts new file mode 100644 index 00000000..2b3117ff --- /dev/null +++ b/src/app/pages/website/back-portfolio/back-portfolio.component.ts @@ -0,0 +1,12 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-back-portfolio', + standalone: true, + imports: [], + templateUrl: './back-portfolio.component.html', + styleUrl: './back-portfolio.component.scss' +}) +export class BackPortfolioComponent { + +} diff --git a/src/app/pages/website/cloud-portfolio/cloud-portfolio.component.html b/src/app/pages/website/cloud-portfolio/cloud-portfolio.component.html new file mode 100644 index 00000000..7e3ae0b1 --- /dev/null +++ b/src/app/pages/website/cloud-portfolio/cloud-portfolio.component.html @@ -0,0 +1,16 @@ +
+
+
+ +
+
+

Cloud

+

Potato Disease Detection

+

Python / FastAPI / Jupyter Notebook / NumPy / TensorFlow / Keras / Google Cloud Platform / Postman

+

+
Created a Convolutional Neural Network (CNN) model capable of identifying early blight and late blight diseases in images.
+
Offered and hosted the API solution that provides predictions on whether a potato has early blight or late blight disease on the Google Cloud Platform via Cloud Functions.
+

+
+
+
\ No newline at end of file diff --git a/src/app/pages/website/cloud-portfolio/cloud-portfolio.component.scss b/src/app/pages/website/cloud-portfolio/cloud-portfolio.component.scss new file mode 100644 index 00000000..383adaa5 --- /dev/null +++ b/src/app/pages/website/cloud-portfolio/cloud-portfolio.component.scss @@ -0,0 +1,49 @@ +::ng-deep .p-galleria-indicators .p-galleria-indicator button { + /* ::ng-deep is used to penetrate the Shadow DOM and target the internal styles of the PrimeNG Carousel component. */ + /*.p-carousel-indicators .p-carousel-indicator button targets the buttons representing the navigation dots.*/ + /*.p-carousel-indicator.p-highlight button targets the button representing the active navigation dot.*/ + width: 15px; /* Adjust the width of the dots */ + height: 15px; /* Adjust the height of the dots */ + border-radius: 50%; /* Make the dots circular */ + background-color: #ccc; /* Set the background color for inactive dots */ + } + + /* Style for the active navigation dot */ +::ng-deep .p-galleria-indicators .p-galleria-indicator.p-highlight button { + background-color: #dac5a7; /* Set the background color for the active dot */ +} + +.galleria-container{ + width: 640px; + height: 640px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.portfolio-container{ + width: 100%; + height: 600px; + margin-bottom: 10rem; + align-items: center; + .detail-container{ + height: 100%; + width: 560px; + display: flex; + flex-direction: column; + justify-content: center; + + .portfolio-type{ + color: #dac5a7; + } + .portfolio-name{ + color: white; + font-size: 3rem; + font-weight: bold; + } + .description{ + color: #7f7466; + } + } +} diff --git a/src/app/pages/website/cloud-portfolio/cloud-portfolio.component.spec.ts b/src/app/pages/website/cloud-portfolio/cloud-portfolio.component.spec.ts new file mode 100644 index 00000000..7c892d24 --- /dev/null +++ b/src/app/pages/website/cloud-portfolio/cloud-portfolio.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CloudPortfolioComponent } from './cloud-portfolio.component'; + +describe('CloudPortfolioComponent', () => { + let component: CloudPortfolioComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [CloudPortfolioComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(CloudPortfolioComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/website/cloud-portfolio/cloud-portfolio.component.ts b/src/app/pages/website/cloud-portfolio/cloud-portfolio.component.ts new file mode 100644 index 00000000..a8e89204 --- /dev/null +++ b/src/app/pages/website/cloud-portfolio/cloud-portfolio.component.ts @@ -0,0 +1,12 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-cloud-portfolio', + standalone: true, + imports: [], + templateUrl: './cloud-portfolio.component.html', + styleUrl: './cloud-portfolio.component.scss' +}) +export class CloudPortfolioComponent { + +} diff --git a/src/app/pages/website/data-portfolio/data-portfolio.component.html b/src/app/pages/website/data-portfolio/data-portfolio.component.html new file mode 100644 index 00000000..9532b803 --- /dev/null +++ b/src/app/pages/website/data-portfolio/data-portfolio.component.html @@ -0,0 +1,36 @@ +
+
+
+ + +
+ +
+
+
+
+
+

Data dashboard & Analytics

+

Stack Overflow Developer 2022 Survey Visualization & Analytics

+

IBM Cognos Analytics / Data Visualization / Data Analytics

+

+ Analyze the data from the Stack Overflow Developer 2022 survey to determine the top technical skills and the most desired technical stack in the coming year. +

+
+
+ +
+
+ +
+
+

Data dashboard & Analytics

+

Kaggle LinkedIn Job Postings data Visualization & Analytics

+

Tableau / Data Visualization / Data Analytics

+ Link +

+ Examine the LinkedIn Job Postings dataset from Kaggle to identify the leading job titles and the most sought-after positions, as well as prevalent industry sectors among companies. +

+
+
+
\ No newline at end of file diff --git a/src/app/pages/website/data-portfolio/data-portfolio.component.scss b/src/app/pages/website/data-portfolio/data-portfolio.component.scss new file mode 100644 index 00000000..383adaa5 --- /dev/null +++ b/src/app/pages/website/data-portfolio/data-portfolio.component.scss @@ -0,0 +1,49 @@ +::ng-deep .p-galleria-indicators .p-galleria-indicator button { + /* ::ng-deep is used to penetrate the Shadow DOM and target the internal styles of the PrimeNG Carousel component. */ + /*.p-carousel-indicators .p-carousel-indicator button targets the buttons representing the navigation dots.*/ + /*.p-carousel-indicator.p-highlight button targets the button representing the active navigation dot.*/ + width: 15px; /* Adjust the width of the dots */ + height: 15px; /* Adjust the height of the dots */ + border-radius: 50%; /* Make the dots circular */ + background-color: #ccc; /* Set the background color for inactive dots */ + } + + /* Style for the active navigation dot */ +::ng-deep .p-galleria-indicators .p-galleria-indicator.p-highlight button { + background-color: #dac5a7; /* Set the background color for the active dot */ +} + +.galleria-container{ + width: 640px; + height: 640px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.portfolio-container{ + width: 100%; + height: 600px; + margin-bottom: 10rem; + align-items: center; + .detail-container{ + height: 100%; + width: 560px; + display: flex; + flex-direction: column; + justify-content: center; + + .portfolio-type{ + color: #dac5a7; + } + .portfolio-name{ + color: white; + font-size: 3rem; + font-weight: bold; + } + .description{ + color: #7f7466; + } + } +} diff --git a/src/app/pages/website/data-portfolio/data-portfolio.component.spec.ts b/src/app/pages/website/data-portfolio/data-portfolio.component.spec.ts new file mode 100644 index 00000000..59b9d3b8 --- /dev/null +++ b/src/app/pages/website/data-portfolio/data-portfolio.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DataPortfolioComponent } from './data-portfolio.component'; + +describe('DataPortfolioComponent', () => { + let component: DataPortfolioComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [DataPortfolioComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(DataPortfolioComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/website/data-portfolio/data-portfolio.component.ts b/src/app/pages/website/data-portfolio/data-portfolio.component.ts new file mode 100644 index 00000000..8f611468 --- /dev/null +++ b/src/app/pages/website/data-portfolio/data-portfolio.component.ts @@ -0,0 +1,32 @@ +import { Component } from '@angular/core'; +import { GalleriaModule } from 'primeng/galleria'; +import { GalleriaImage } from '@app/models/galleriaImage.model'; + +@Component({ + selector: 'app-data-portfolio', + standalone: true, + imports: [GalleriaModule], + templateUrl: './data-portfolio.component.html', + styleUrl: './data-portfolio.component.scss' +}) +export class DataPortfolioComponent { + responsiveOptions: any[] = [ + { + breakpoint: '1024px', + numVisible: 5 + }, + { + breakpoint: '768px', + numVisible: 3 + }, + { + breakpoint: '560px', + numVisible: 1 + } +]; + dataImages: GalleriaImage[] = [ + new GalleriaImage( "assets/data-1.png"), + new GalleriaImage( "assets/data-2.png"), + new GalleriaImage( "assets/data-3.png"), + ]; +} diff --git a/src/app/pages/website/experience/experience.component.html b/src/app/pages/website/experience/experience.component.html new file mode 100644 index 00000000..29efc571 --- /dev/null +++ b/src/app/pages/website/experience/experience.component.html @@ -0,0 +1,98 @@ +
+

work experience and education

+

What I Do?

+
+
    +
  • +
    + +

    Student Internship

    +

    Inspur Group

    +

    • Collaboration: Successfully collaborated within the Artificial Intelligence Team.

    +

    • Data Visualization: Created impactful radar and line charts that significantly contributed to data-driven currency prediction. These visualizations provided compelling and insightful representations of complex information.

    +
    +
  • +
  • +
    + +

    Graduated in The Chinese university of Hong Kong

    +

    Bachelor of Science in Mathematics and Information Engineering

    +

    Minor in Computer Science

    +
    +
  • +
  • +
    + +

    Backend Software Developer

    +

    A.S. Watson Group - Elab(Asia) │ Hong Kong

    +

    • Developed and maintained a large-scale eCommerce system using Java Spring.

    +

    • Conducted rigorous performance evaluations with JMeter.

    +

    • Implemented Google Cloud’s Apigee API Management for efficient RESTful APIs.

    +

    • Utilized Git for code versioning and collaborated using Atlassian tools.

    +

    • Create a docker image for CICD

    +

    • Compose queries to access data in databases

    +
    +
  • +
  • +
    + +

    Full-Stack Software Developer

    +

    DigiDumpling Limited│ Hong Kong, China

    +

    • Managed and optimized databases using MySQL Workbench and Oracle Database, resulting in streamlined databases, optimized performance.

    +

    • Created high-quality Android and iOS mobile applications for the bank using React Native, built backend API solutions using Java with the Spring Boot framework, and deployed backend API services in Azure to provide scalability and high performance.

    +

    • Added new functionality to projects based on Vue.js and React.js

    +

    • Contributed collaboratively in an Agile startup team environment.

    +

    • Experience with managing state, using libraries like Redux.

    +

    • Successfully implemented a robust notification center within the mobile application using Firebase services.

    +
    +
  • +
  • +
    + +

    Google Data Analytics Professional Certificate

    +

    • Include hands-on, practice-based assessments and are designed to prepare them for introductory-level roles in Data Analytics. They are competent in tools and platforms including spreadsheets, SQL, Tableau, and R. They know how to prepare, process, analyze, and share data for thoughtful action.

    +
    +
  • +
  • +
    + +

    Microsoft Azure AI Fundamentals Professional Certificate (AI-900)

    +
    +
  • +
  • +
    + +

    IBM Data Analyst Professional Certificate

    +

    • Understands the core principles of data analysis and has worked hands-on with a variety of data sources, project scenarios, and data analysis tools, including Excel, SQL, Relational Databases, Python, Jupyter Notebooks, and Cognos Analytics, gaining practical experience with data manipulation, data analysis, and data visualization.

    +

    • Proficiency in applying different analytical techniques by analyzing real-world datasets, creating visualizations & interactive dashboards, and presenting reports to share findings of data analysis, and is now equipped with skills for an entry-level role in data analytics.

    +
    +
  • +
  • +
    + +

    Junior Data Analyst Program

    +

    NPower Canada │ Toronto, ON

    +

    14-week intensive online training on the fundamentals of computer technology, and project management

    +

    • Develop a working knowledge of Python language for data analysis using Pandas and Numpy.

    +

    • Visualize data using Python libraries including Matplotlib, Seaborn, Plotly, and Dash.

    +

    • Utilize Excel spreadsheets to perform data wrangling and data mining.

    +

    • Explain cloud concepts, benefits of cloud computing, and core Azure architecture components.

    +

    • Use core Azure services and choose the Azure AI services that best address a company’s challenges.

    +

    • Control Azure spending and managing bills by applying recommended practices to minimize cost.

    +
    +
  • +
  • +
    + +

    Volunteer

    +

    105 Gibson Centre │ Toronto, ON

    +

    • Admin support, campaign, and event helpers are needed.

    +

    • Event set up, take down & clean up, greeting.

    +

    • Welcome guests and clients. Answer phone and client inquiries. Provide administrative support and data entry

    +

    • Perform handy work. Repair various equipment in the centre. Assist with building maintenance.

    +
    +
  • +
+ +
+
\ No newline at end of file diff --git a/src/app/pages/website/experience/experience.component.scss b/src/app/pages/website/experience/experience.component.scss new file mode 100644 index 00000000..b35f3553 --- /dev/null +++ b/src/app/pages/website/experience/experience.component.scss @@ -0,0 +1,85 @@ + +.experience-container { + background-color: #000000; + color: #dac5a7; +} + +ul { + padding: 50px 0; +} + +ul li { + position:relative; + list-style-type: none; + width: 6px; + background-color: white; + margin: 0 auto; + padding-top: 50px; +} + +ul li::after { + background: white; + content: ''; + width: 25px; + height: 25px; + border-radius: 50%; + position:absolute; + left:50%; + transform: translateX(-50%); + bottom: 0; + } + + +ul li div{ + position: relative; + width: 400px; + padding: 15px; + border-radius: 5px; + bottom: 0; + background: #22211e; +} + +ul li:nth-child(odd) div { + left: 45px; +} + +ul li:nth-child(even) div { + left: -445px; +} + +time { + font-weight: bold; + font-size: 18px; + display: block; + margin-bottom: 10px; +} + +ul li div { + visibility: hidden; + opacity: 0; + transition: all .5s ease-in-out; +} + +ul li:nth-child(odd) div { + transform: translate3d(200px, 0, 0); +} + +ul li:nth-child(even) div { + transform: translate3d(-200px, 0, 0); +} + +ul li.show div { + transform: none; + visibility: visible; + opacity: 1; +} + +@media screen and (max-width: 900px) { + ul li div { + width: 250px; + } + ul li:nth-child(even) div { + left: -289px; + /*250+45-6*/ + } +} \ No newline at end of file diff --git a/src/app/pages/website/experience/experience.component.spec.ts b/src/app/pages/website/experience/experience.component.spec.ts new file mode 100644 index 00000000..7db524cb --- /dev/null +++ b/src/app/pages/website/experience/experience.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ExperienceComponent } from './experience.component'; + +describe('ExperienceComponent', () => { + let component: ExperienceComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ExperienceComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ExperienceComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/website/experience/experience.component.ts b/src/app/pages/website/experience/experience.component.ts new file mode 100644 index 00000000..a44e1902 --- /dev/null +++ b/src/app/pages/website/experience/experience.component.ts @@ -0,0 +1,38 @@ +import { Component, ElementRef, ViewChild, HostListener } from '@angular/core'; + +@Component({ + selector: 'app-experience', + standalone: true, + imports: [], + templateUrl: './experience.component.html', + styleUrl: './experience.component.scss' +}) +export class ExperienceComponent{ + name = 'Angular'; + @ViewChild('timeline', { static: true }) timeline!: ElementRef; + + constructor() { } + + @HostListener('window:scroll', ['$event']) + onWindowScroll(event: Event) { + // Get the native element of the ViewChild + const ulElement = this.timeline.nativeElement; + + // Get all child li elements + const liElements = ulElement.querySelectorAll('li'); + + // Check if each li element is in the viewport + liElements.forEach((li: HTMLElement) => { + const rect = li.getBoundingClientRect(); + const isVisible = rect.top < window.innerHeight && rect.bottom >= 0; + + // Add or remove the "scrolled" class based on visibility + if (isVisible) { + li.classList.add('show'); + } else { + //li.classList.remove('show'); + } + }); + } + +} diff --git a/src/app/pages/website/front-portfolio/front-portfolio.component.html b/src/app/pages/website/front-portfolio/front-portfolio.component.html new file mode 100644 index 00000000..57ec83ab --- /dev/null +++ b/src/app/pages/website/front-portfolio/front-portfolio.component.html @@ -0,0 +1,59 @@ +
+
+
+ + +
+ +
+
+
+
+
+

Web & Mobile Applications

+

Wealth Management App

+

JavaScript / React Native / Redux / React Hooks / CSS / Java / Gradle / Node.js / Xcode / Android Studio / Firebase

+

+
A mobile application for the banks named Delta Asia Bank Limited(BDA), which is a bank in Macau. Build with React Native.
+
The mobile app includes several functions, deposit, withdrawal, fixed deposit, fund transfer (allow different currency), Biometric Authentication, 2FA Auth, security pin, HKID capture, pdf download, document upload, phone notification, etc.
+
Supported three languages.

+
+
+ +
+
+ + +
+ +
+
+
+
+
+

Web & Mobile Applications

+

Financial App

+

TypeScript / React Native / Redux / React Hooks / CSS / Java / Gradle / Node.js / npm / Xcode / Android Studio / Firebase

+

+
A mobile application for the financial named AGBA Group Holding Ltd, under Convoy Global Holdings Limited. Build with React Native.
+
The mobile app includes several functions, deposit, withdrawal, Biometric Authentication, 2FA Auth, security pin, HKID capture, pdf download, document upload, etc. Supported three languages.
+

+
+
+ +
+
+ + + +
+
+

Web & Mobile Applications

+

Personal Portfolio Website

+

Angular / SCSS / Bootstrap / npm / UI/UX Design / TypeScript

+

+
I'm excited for you to explore my portfolio website, which was a personal web project I took on from start to finish.
+
I handled everything from the UI/UX design and development to the deployment process. Please take your time reviewing it in detail - I'd appreciate any feedback you may have as I'm always looking to enhance my skills.

+
+
+
\ No newline at end of file diff --git a/src/app/pages/website/front-portfolio/front-portfolio.component.scss b/src/app/pages/website/front-portfolio/front-portfolio.component.scss new file mode 100644 index 00000000..383adaa5 --- /dev/null +++ b/src/app/pages/website/front-portfolio/front-portfolio.component.scss @@ -0,0 +1,49 @@ +::ng-deep .p-galleria-indicators .p-galleria-indicator button { + /* ::ng-deep is used to penetrate the Shadow DOM and target the internal styles of the PrimeNG Carousel component. */ + /*.p-carousel-indicators .p-carousel-indicator button targets the buttons representing the navigation dots.*/ + /*.p-carousel-indicator.p-highlight button targets the button representing the active navigation dot.*/ + width: 15px; /* Adjust the width of the dots */ + height: 15px; /* Adjust the height of the dots */ + border-radius: 50%; /* Make the dots circular */ + background-color: #ccc; /* Set the background color for inactive dots */ + } + + /* Style for the active navigation dot */ +::ng-deep .p-galleria-indicators .p-galleria-indicator.p-highlight button { + background-color: #dac5a7; /* Set the background color for the active dot */ +} + +.galleria-container{ + width: 640px; + height: 640px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.portfolio-container{ + width: 100%; + height: 600px; + margin-bottom: 10rem; + align-items: center; + .detail-container{ + height: 100%; + width: 560px; + display: flex; + flex-direction: column; + justify-content: center; + + .portfolio-type{ + color: #dac5a7; + } + .portfolio-name{ + color: white; + font-size: 3rem; + font-weight: bold; + } + .description{ + color: #7f7466; + } + } +} diff --git a/src/app/pages/website/front-portfolio/front-portfolio.component.spec.ts b/src/app/pages/website/front-portfolio/front-portfolio.component.spec.ts new file mode 100644 index 00000000..3ace0819 --- /dev/null +++ b/src/app/pages/website/front-portfolio/front-portfolio.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FrontPortfolioComponent } from './front-portfolio.component'; + +describe('FrontPortfolioComponent', () => { + let component: FrontPortfolioComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [FrontPortfolioComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(FrontPortfolioComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/website/front-portfolio/front-portfolio.component.ts b/src/app/pages/website/front-portfolio/front-portfolio.component.ts new file mode 100644 index 00000000..c9d5d1a2 --- /dev/null +++ b/src/app/pages/website/front-portfolio/front-portfolio.component.ts @@ -0,0 +1,62 @@ +import { Component } from '@angular/core'; +import { GalleriaModule } from 'primeng/galleria'; +import { GalleriaImage } from '@app/models/galleriaImage.model'; + +@Component({ + selector: 'app-front-portfolio', + standalone: true, + imports: [GalleriaModule], + templateUrl: './front-portfolio.component.html', + styleUrl: './front-portfolio.component.scss' +}) +export class FrontPortfolioComponent { + + responsiveOptions: any[] = [ + { + breakpoint: '1024px', + numVisible: 5 + }, + { + breakpoint: '768px', + numVisible: 3 + }, + { + breakpoint: '560px', + numVisible: 1 + } +]; + bdaImages: GalleriaImage[] = [ + new GalleriaImage( "assets/bda-1.png"), + new GalleriaImage( "assets/bda-2.png"), + new GalleriaImage( "assets/bda-3.png"), + new GalleriaImage( "assets/bda-4.png"), + new GalleriaImage( "assets/bda-5.png"), + new GalleriaImage( "assets/bda-6.png"), + new GalleriaImage( "assets/bda-7.png"), + new GalleriaImage( "assets/bda-8.png"), + new GalleriaImage( "assets/bda-9.png"), + new GalleriaImage( "assets/bda-10.png"), + new GalleriaImage( "assets/bda-11.png") + ]; + + agbaImages: GalleriaImage[] = [ + new GalleriaImage( "assets/agba-1.png"), + new GalleriaImage( "assets/agba-2.png"), + new GalleriaImage( "assets/agba-3.png"), + new GalleriaImage( "assets/agba-4.png"), + new GalleriaImage( "assets/agba-5.png"), + new GalleriaImage( "assets/agba-6.png"), + new GalleriaImage( "assets/agba-7.png"), + new GalleriaImage( "assets/agba-8.png"), + new GalleriaImage( "assets/agba-9.png"), + new GalleriaImage( "assets/agba-10.png"), + new GalleriaImage( "assets/agba-11.png"), + new GalleriaImage( "assets/agba-12.png"), + new GalleriaImage( "assets/agba-13.png"), + new GalleriaImage( "assets/agba-14.png"), + new GalleriaImage( "assets/agba-15.png"), + new GalleriaImage( "assets/agba-16.png"), + ]; + + +} diff --git a/src/app/pages/website/landing/landing.component.html b/src/app/pages/website/landing/landing.component.html new file mode 100644 index 00000000..6f851826 --- /dev/null +++ b/src/app/pages/website/landing/landing.component.html @@ -0,0 +1,75 @@ + + + + +
+
+

Hello

+

.

+
+
+

I am Lucas Ho

+
+

Professional Software Developer

+ + + + + + +

Download CV

+
+
+
+ +
+
+

+ I am a Full-Stack Software Developer. + Demonstrates expertise in designing, developing, and deploying diverse software, specializing in Mobile Applications, Database Management Systems, E-commerce Platforms, API Integrations, and Cloud-Based Solutions +

+ + +
+
+ + + + + + +
+

+ Copyright 2024 All Right Reserved +

+ +
\ No newline at end of file diff --git a/src/app/pages/website/landing/landing.component.scss b/src/app/pages/website/landing/landing.component.scss new file mode 100644 index 00000000..fbbbb814 --- /dev/null +++ b/src/app/pages/website/landing/landing.component.scss @@ -0,0 +1,191 @@ +* { + font-family: 'Montserrat', sans-serif !important; + } + +.top-bar{ + background-color: #dac5a7; + height: 115px; + width: 100%; +} + +.navbar-brand{ + font-weight: bold; +} + +.nav-item-text{ + font-size: larger; + font-weight: 400; +} + +.navbar{ + position: fixed; + top: 0; + width: 100%; + height: 105px; + z-index: 1000; +} + +.hover-underline-animation { + display: inline-block; + position: relative; + color: #0087ca; + } + +.hover-underline-animation::after { + content: ''; + position: absolute; + width: 100%; + transform: scaleX(0); + height: 2px; + bottom: 0; + left: 0; + background-color: black; + transform-origin: bottom right; + transition: transform 0.25s ease-out; +} + +.hover-underline-animation:hover::after { + transform: scaleX(1); + transform-origin: bottom left; +} + +.brand-img{ + width: 250px; + height: 30px; +} + +.me-img{ + width: 500px; + height: 750px; + align-self: center; +} + +.me-background-box{ + background-color: #dac5a7; + height: 100%; + width: 30%; + display: flex; + flex-direction: column; + justify-content: end; +} + +.self-intro-container{ + background-color: #000000; + width: 100%; + height: 800px; + margin-top: 100px; +} + +.hello-box{ + width: 30%; +} + +.self-intro-box{ + width: 30%; +} + +.line-box{ + background-color: #dac5a7; + width: 45px; + height: 5px; + align-self: center; +} + +.download-button{ + margin-top: 20px; + box-sizing: inherit; + transition-property: all; + transition-duration: .6s; + transition-timing-function: ease; + cursor: pointer; + line-height: 45px; + width: 300px; + height: 60px; + position: relative; + text-decoration: none; + text-transform: uppercase; + border-radius: 10px; + + &:hover { text-decoration: none; } +} + +svg { + fill: none; + stroke: #fff; + stroke-dasharray: 150 480; + stroke-dashoffset: 150; + transition: 1s ease-in-out; +} + +.download-button-1 { + background: #dac5a7; + font-weight: 100; + .download-light-img { + display: none; + } + + .download-text{ + font-size: 20px; + font-weight: 200; + color: #000000; + } + + svg { + height: 55px; + left: 0; + position: absolute; + top: 0; + width: 100%; + } + + rect { + fill: none; + stroke: #dac5a7; + stroke-width: 0; + stroke-dasharray: 422, 0; + transition: all 0.35s linear; + } +} + +.download-button:hover { + letter-spacing: 1px; + background-color: #000000; + + rect { + stroke-width: 5; + stroke-dasharray: 15, 310; + stroke-dashoffset: 48; + transition: all 1.35s cubic-bezier(0.19, 1, 0.22, 1); + } + + .download-text{ + color: #dac5a7; + font-weight: 900; + } + + .download-light-img { + display: block; + } + + .download-black-img { + display: none; + } + } + +.rooter-container{ + background-color: #dac5a7; + font-size: 18px; + font-weight: normal; + height: 70px; + width: 100%; +} + +.social-img{ + height: 40px; + width: 40px; +} + +.social-img-container{ + height: 40px; + width: 100px; +} \ No newline at end of file diff --git a/src/app/pages/website/landing/landing.component.spec.ts b/src/app/pages/website/landing/landing.component.spec.ts new file mode 100644 index 00000000..5ed703f1 --- /dev/null +++ b/src/app/pages/website/landing/landing.component.spec.ts @@ -0,0 +1,23 @@ +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({ + imports: [LandingComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(LandingComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/website/landing/landing.component.ts b/src/app/pages/website/landing/landing.component.ts new file mode 100644 index 00000000..d9e305d1 --- /dev/null +++ b/src/app/pages/website/landing/landing.component.ts @@ -0,0 +1,35 @@ +import { Component, ViewChild } from '@angular/core'; +import { AboutMeComponent } from '../about-me/about-me.component'; +import { ExperienceComponent } from '../experience/experience.component'; +import { TechnicalSkillsComponent } from '../technical-skills/technical-skills.component'; +import { PortfolioComponent } from '../portfolio/portfolio.component'; + +@Component({ + selector: 'app-landing', + standalone: true, + imports: [AboutMeComponent, ExperienceComponent, TechnicalSkillsComponent, PortfolioComponent], + templateUrl: './landing.component.html', + styleUrl: './landing.component.scss' +}) +export class LandingComponent { + @ViewChild(AboutMeComponent) + aboutMeComponent!: AboutMeComponent; + + @ViewChild(PortfolioComponent) + portfolioComponent!: PortfolioComponent; + + @ViewChild(TechnicalSkillsComponent) + technicalSkillsComponent!: TechnicalSkillsComponent; + + scrollToAboutMe() { + this.aboutMeComponent.scrollTo(); + } + + scrollToPortfolio() { + this.portfolioComponent.scrollTo(); + } + + scrollToTechnicalSkills() { + this.technicalSkillsComponent.scrollTo(); + } +} diff --git a/src/app/pages/website/portfolio/portfolio.component.html b/src/app/pages/website/portfolio/portfolio.component.html new file mode 100644 index 00000000..7078f3ad --- /dev/null +++ b/src/app/pages/website/portfolio/portfolio.component.html @@ -0,0 +1,29 @@ +
+
+

PORTFOLIO

+

Latest Projects

+
+
+
+ Web & Mobile Applications +
+
+ Backend & APIs +
+
+ Data dashboard & Analytics +
+
+ Cloud +
+
+ +
+ + + + +
+ +
+
\ No newline at end of file diff --git a/src/app/pages/website/portfolio/portfolio.component.scss b/src/app/pages/website/portfolio/portfolio.component.scss new file mode 100644 index 00000000..71534274 --- /dev/null +++ b/src/app/pages/website/portfolio/portfolio.component.scss @@ -0,0 +1,59 @@ + +.portfolio-container{ + background-color: #000000; + color: #dac5a7; + width: 100%; + max-height: 3000px; +} + +.inner-container{ + background-color: #22211e; + border-top-left-radius: 8rem; + border-top-right-radius: 8rem; + width: 100%; + height: 100%; +} + +.vertical-box{ + margin-top: 10px; + width: 4px; + height: 60px; + background-color: #dac5a7; + border-radius: 5px; +} + +.tab-container{ + width: 1200px; + height: 100px; + margin-top: 50px; + border-radius: 10rem; + border: solid 1px #7f7466; + font-size: 1.5rem; + margin-bottom: 150px; + .normal-tab{ + border-radius: 10rem; + display: flex; + flex-direction: column; + justify-content: center; + width: 280px; + height: 80px; + text-align: center; + cursor: pointer; + } + + .selected-tab{ + background-color: #dac5a7; + color: black; + transition: all .5s ease-in-out; + } + + .tab{ + color: #a79783; + } + .tab:hover{ + color: white; + transform: translate3d(0, -10px, 0); + transition: all .2s ease-in-out; + } +} + diff --git a/src/app/pages/website/portfolio/portfolio.component.spec.ts b/src/app/pages/website/portfolio/portfolio.component.spec.ts new file mode 100644 index 00000000..8e039d58 --- /dev/null +++ b/src/app/pages/website/portfolio/portfolio.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PortfolioComponent } from './portfolio.component'; + +describe('PortfolioComponent', () => { + let component: PortfolioComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [PortfolioComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(PortfolioComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/website/portfolio/portfolio.component.ts b/src/app/pages/website/portfolio/portfolio.component.ts new file mode 100644 index 00000000..11a778f8 --- /dev/null +++ b/src/app/pages/website/portfolio/portfolio.component.ts @@ -0,0 +1,28 @@ +import { Component, ElementRef } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FrontPortfolioComponent } from '../front-portfolio/front-portfolio.component'; +import { BackPortfolioComponent } from '../back-portfolio/back-portfolio.component'; +import { DataPortfolioComponent } from '../data-portfolio/data-portfolio.component'; +import { CloudPortfolioComponent } from '../cloud-portfolio/cloud-portfolio.component'; + +@Component({ + selector: 'app-portfolio', + standalone: true, + imports: [CommonModule, FrontPortfolioComponent, BackPortfolioComponent, DataPortfolioComponent, CloudPortfolioComponent], + templateUrl: './portfolio.component.html', + styleUrl: './portfolio.component.scss' +}) +export class PortfolioComponent { + selectedPortfolioType: string = "frontend"; + portfolioType: string[] = ["frontend", "backend", "data", "cloud"]; + + selectType(type: string): void { + this.selectedPortfolioType = type; + } + + constructor(private elementRef: ElementRef) {} + + scrollTo() { + this.elementRef.nativeElement.scrollIntoView({ behavior: 'smooth', block: 'start' }); + } +} diff --git a/src/app/pages/website/technical-skills/technical-skills.component.html b/src/app/pages/website/technical-skills/technical-skills.component.html new file mode 100644 index 00000000..9220f3cb --- /dev/null +++ b/src/app/pages/website/technical-skills/technical-skills.component.html @@ -0,0 +1,19 @@ +
+

my technical skills

+

What Is My Skills?

+
+ +
\ No newline at end of file diff --git a/src/app/pages/website/technical-skills/technical-skills.component.scss b/src/app/pages/website/technical-skills/technical-skills.component.scss new file mode 100644 index 00000000..2ea234e6 --- /dev/null +++ b/src/app/pages/website/technical-skills/technical-skills.component.scss @@ -0,0 +1,117 @@ +.technical-skills-container { + background-color: #000000; + color: #dac5a7; + width: 100%; + height: 1000px; +} + +.vertical-box{ + width: 6px; + height: 100px; + background-color: #dac5a7; + border-radius: 5px; +} + +.carousel-container{ + width: 100%; + height: 100%; + margin-top: 100px; +} + +::ng-deep .p-carousel-indicators .p-carousel-indicator button { + /* ::ng-deep is used to penetrate the Shadow DOM and target the internal styles of the PrimeNG Carousel component. */ + /*.p-carousel-indicators .p-carousel-indicator button targets the buttons representing the navigation dots.*/ + /*.p-carousel-indicator.p-highlight button targets the button representing the active navigation dot.*/ + width: 15px; /* Adjust the width of the dots */ + height: 15px; /* Adjust the height of the dots */ + border-radius: 50%; /* Make the dots circular */ + background-color: #ccc; /* Set the background color for inactive dots */ + } + + /* Style for the active navigation dot */ +::ng-deep .p-carousel-indicators .p-carousel-indicator.p-highlight button { + background-color: #dac5a7; /* Set the background color for the active dot */ +} + + +:host ::ng-deep { + .skill-item { + .skill-item-content { + .horizontal-box{ + width: 70px; + height: 6px; + background-color: #dac5a7; + border-radius: 5px; + transition: background-color 0.5s ease; + } + .skill-id{ + font-weight: bold; + font-size: 3rem; + color: rgba(255, 255, 255, 0.2); + transition: color 0.5s ease; + } + .skill-name{ + height: 100px; + font-weight: 600; + font-size: 1.5rem; + color: white; + margin-bottom: 10px; + } + .skill-description{ + height: 200px; + } + display: flex; + flex-direction: column; + justify-content: flex-start; + border: 1px solid var(--surface-d); + border-color: #dac5a7; + margin: 1.3rem; + text-align: start; + padding: 2rem; + height: 450px; + border-radius: 2rem; + color: #dac5a7; + font-size: 1rem; + letter-spacing: .15rem; + transition: all .5s; + position: relative; + overflow: hidden; + z-index: 1; + &:after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + background-color: #22211e; + border-radius: 5px; + z-index: -2; + } + &:before { + content: ''; + position: absolute; + bottom: 0; + left: 0; + height: 0px; + width: 100%; + background-color: #dac5a7; + transition: all .5s; + border-radius: 5px; + z-index: -1; + } + &:hover { + color: black; + .horizontal-box { + background-color: black; + } + .skill-name { + color: black; + } + &:before { + height: 100%; + } + } + } + } +} \ No newline at end of file diff --git a/src/app/pages/website/technical-skills/technical-skills.component.spec.ts b/src/app/pages/website/technical-skills/technical-skills.component.spec.ts new file mode 100644 index 00000000..027a7d13 --- /dev/null +++ b/src/app/pages/website/technical-skills/technical-skills.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TechnicalSkillsComponent } from './technical-skills.component'; + +describe('TechnicalSkillsComponent', () => { + let component: TechnicalSkillsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [TechnicalSkillsComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(TechnicalSkillsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/website/technical-skills/technical-skills.component.ts b/src/app/pages/website/technical-skills/technical-skills.component.ts new file mode 100644 index 00000000..d3041d95 --- /dev/null +++ b/src/app/pages/website/technical-skills/technical-skills.component.ts @@ -0,0 +1,31 @@ +import { Component, ElementRef } from '@angular/core'; +import { CarouselModule } from 'primeng/carousel'; +import { Skill } from '@app/models/skill.model'; +//import { Skill } from '../../../models/skill.model'; + +@Component({ + selector: 'app-technical-skills', + standalone: true, + imports: [CarouselModule], + templateUrl: './technical-skills.component.html', + styleUrl: './technical-skills.component.scss' +}) +export class TechnicalSkillsComponent { + skills: Skill[] = [ + new Skill(1, 'Programming Languages', 'SQL, Python, R, Java, Kotlin, TypeScript, JavaScript, CSS, SCSS, HTML5'), + new Skill(2, 'Frameworks & Libraries', 'Spring, Spring Boot, React.js, React Native, Node.js, Redux, Vue.js, Bootstrap, Angular, Node.js'), + new Skill(3, 'Database', 'MySQL, Oracle, MSSQL'), + new Skill(4, 'Cloud Platform', 'Azure, AWS, Google Cloud Apigee, Google Cloud Platform, Firebase'), + new Skill(5, 'Applications & Tools', 'Postman, Microsoft Excel, Azure Sandbox, Google Sheet, Xcode, Docker, Tableau, Figma'), + new Skill(6, 'Tools', 'Google Workspace, RDBMS, IBM Cognos Analytics, Jupyter, SQLite'), + new Skill(7, 'Operating Systems', 'Windows 10, Linux'), + new Skill(8, 'Project Management Fundamentals', 'Agile, Waterfall, Scrum, Kanban, Trello'), + new Skill(9, 'Languages', 'English, Mandarin and Cantonese'), + ]; + + constructor(private elementRef: ElementRef) {} + + scrollTo() { + this.elementRef.nativeElement.scrollIntoView({ behavior: 'smooth', block: 'start' }); + } +}