Add initial Angular application structure and components
This commit is contained in:
@@ -0,0 +1 @@
|
||||
<router-outlet></router-outlet>
|
||||
@@ -0,0 +1,30 @@
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {AppComponent} from './app.component';
|
||||
|
||||
describe('AppComponent', (): void => {
|
||||
beforeEach(async (): Promise<void> => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [AppComponent],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
it('should create the app', (): void => {
|
||||
const fixture: ComponentFixture<AppComponent> = TestBed.createComponent(AppComponent);
|
||||
const app: AppComponent = fixture.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should have the 'Angular-Portfolio-Seite' title`, (): void => {
|
||||
const fixture: ComponentFixture<AppComponent> = TestBed.createComponent(AppComponent);
|
||||
const app: AppComponent = fixture.componentInstance;
|
||||
expect(app.title).toEqual('Angular-Portfolio-Seite');
|
||||
});
|
||||
|
||||
it('should render title', (): void => {
|
||||
const fixture: ComponentFixture<AppComponent> = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.nativeElement as HTMLElement;
|
||||
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, Angular-Portfolio-Seite');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import {Component} from '@angular/core'
|
||||
import {RouterOutlet} from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
imports: [
|
||||
RouterOutlet
|
||||
],
|
||||
templateUrl: './app.component.html'
|
||||
})
|
||||
export class AppComponent {
|
||||
title: string = "Angular-Portfolio-Seite";
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import {mergeApplicationConfig, ApplicationConfig} from '@angular/core';
|
||||
import {provideServerRendering} from '@angular/platform-server';
|
||||
|
||||
import {appConfig} from './app.config';
|
||||
|
||||
const serverConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideServerRendering(),
|
||||
]
|
||||
};
|
||||
|
||||
export const config: ApplicationConfig = mergeApplicationConfig(appConfig, serverConfig);
|
||||
@@ -0,0 +1,16 @@
|
||||
import {ApplicationConfig, importProvidersFrom, provideZoneChangeDetection} from '@angular/core';
|
||||
import {provideRouter} from '@angular/router';
|
||||
import {BrowserModule, provideClientHydration, withEventReplay} from '@angular/platform-browser';
|
||||
|
||||
import {routes} from './app.routes';
|
||||
import {provideAnimations} from '@angular/platform-browser/animations';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideZoneChangeDetection({eventCoalescing: true}),
|
||||
provideRouter(routes),
|
||||
provideClientHydration(withEventReplay()),
|
||||
importProvidersFrom(BrowserModule),
|
||||
provideAnimations()
|
||||
]
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
import {Routes} from '@angular/router';
|
||||
|
||||
import {HomeComponent} from './home/home.component';
|
||||
import {ImprintComponent} from './imprint/imprint.component';
|
||||
|
||||
export const routes: Routes = [
|
||||
{path: '', component: HomeComponent, pathMatch: 'full'},
|
||||
{path: 'imprint', component: ImprintComponent, pathMatch: 'full'},
|
||||
{path: '**', redirectTo: ''}
|
||||
];
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<noscript>
|
||||
<div class="container">
|
||||
<div class="card">
|
||||
<h2 id="loading-text" class="warning-text">
|
||||
Enable javascript for the full experience!
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</noscript>
|
||||
|
||||
<footer>
|
||||
<p>
|
||||
<a routerLink="/imprint">IMPRINT</a> | © 2023
|
||||
- {{ currentYear }} {{ globalFields.firstname }} {{ globalFields.lastname }} - All rights reserved.
|
||||
</p>
|
||||
</footer>
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { FooterComponent } from './footer.component';
|
||||
|
||||
describe('FooterComponent', () => {
|
||||
let component: FooterComponent;
|
||||
let fixture: ComponentFixture<FooterComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [FooterComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(FooterComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {RouterLink} from "@angular/router";
|
||||
import {globalFields} from '../../global.fields';
|
||||
|
||||
@Component({
|
||||
selector: 'app-footer',
|
||||
imports: [
|
||||
RouterLink
|
||||
],
|
||||
templateUrl: './footer.component.html'
|
||||
})
|
||||
export class FooterComponent {
|
||||
protected readonly globalFields: typeof globalFields = globalFields;
|
||||
|
||||
protected readonly currentYear: number = new Date().getFullYear();
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
<section class="container">
|
||||
<div class="card profile">
|
||||
|
||||
<img ngSrc="Logo.png" (click)="openInfo()" priority width="200" height="200" fetchpriority="high" alt="profile picture"/>
|
||||
<h1>Hallo! 👋 I'm {{ globalFields.firstname }} {{ globalFields.lastname }}!</h1>
|
||||
<h3>📍 Austria, Vienna</h3>
|
||||
|
||||
<br>
|
||||
<hr>
|
||||
|
||||
<div class="max60">
|
||||
<div id="short-bio-text" class="hover-items" (click)="toggleBio()">
|
||||
<fa-icon id="left-arrow" [icon]=" isLongBioShown ? faArrowDown : faArrowRight "></fa-icon>
|
||||
|
||||
<a>
|
||||
<u>
|
||||
<h3>A young enthusiastic developer from Austria!</h3>
|
||||
</u>
|
||||
</a>
|
||||
|
||||
<fa-icon id="right-arrow" [icon]=" isLongBioShown ? faArrowDown : faArrowLeft "></fa-icon>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="long-bio-text" *ngIf="isLongBioShown" @fadeInOut (click)="closeOnOverlayClick($event)">
|
||||
<p>
|
||||
My name is {{ globalFields.firstname }}, many people also call me Julie. I'm {{ age }} years old and come from
|
||||
beautiful Austria.
|
||||
I spend most of my time in trains.
|
||||
<br/>
|
||||
<br/>
|
||||
My biggest hobby apart from software development is diving. Floating in deep water and only thinking
|
||||
about the here and now always brings me back to the depths. I try to practice this hobby as often as
|
||||
possible for fun and my health.
|
||||
<br/>
|
||||
<br/>
|
||||
I currently work mainly for the Viennese company SobIT Gmbh. This company develops software for most
|
||||
care companies in Austria, both for staff scheduling and for patient registration.
|
||||
<br/>
|
||||
<br/>
|
||||
I also work for a company called GERLACH SYSTEMS, which is the owner of the SynRadio and SynHost
|
||||
projects. Their main business is the rental of servers, domains, web servers, game servers, etc. at
|
||||
various locations.
|
||||
<br/>
|
||||
<br/>
|
||||
<a [href]="contactSafeMail">
|
||||
<b><u><h3>Just contact me!</h3></u></b>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<br>
|
||||
|
||||
<div class="bio grid-container">
|
||||
<div class="grid-item-start">
|
||||
<h1>I work with</h1>
|
||||
</div>
|
||||
<div class="grid-item" *ngFor="let bioText of bioTextsList; let i = index" [hidden]="i !== currentIndex">
|
||||
<h1>{{ bioText }}</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="contact-area">
|
||||
<a [href]="contactSafeMail">
|
||||
<span>
|
||||
<fa-icon [icon]="faEnvelope" size="3x"></fa-icon>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<a href="tel:{{globalFields.hrefContactPhone}}">
|
||||
<fa-icon class="fab" [icon]="faPhone" size="3x"></fa-icon>
|
||||
</a>
|
||||
|
||||
<a href="https://discord.com/users/860206216893693973">
|
||||
<fa-icon class="fab" [icon]="faDiscord" size="3x"></fa-icon>
|
||||
</a>
|
||||
|
||||
<a href="https://github.com/fraujulian">
|
||||
<fa-icon class="fab" [icon]="faGithub" size="3x"></fa-icon>
|
||||
</a>
|
||||
|
||||
<a href="https://www.xing.com/profile/Julian_Lechner03274">
|
||||
<fa-icon class="fab" [icon]="faXing" size="3x"></fa-icon>
|
||||
</a>
|
||||
|
||||
<a href="https://www.linkedin.com/in/julian-lechner-98b377356/">
|
||||
<fa-icon class="fab" [icon]="faLinkedin" size="3x"></fa-icon>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div id="github_projects">
|
||||
<div *ngFor="let project of projects">
|
||||
<div class="container" [class.clickable]="project.Clickable" [class.disabled]="!project.Clickable"
|
||||
[style.cursor]="project.Clickable ? 'pointer' : 'default'"
|
||||
(click)="project.Clickable && (project.IsReadmeShown = true)">
|
||||
<div class="card">
|
||||
<div class="media">
|
||||
<div class="media-body">
|
||||
<a href="{{project.Link}}">
|
||||
<u><strong class="d-block text-gray-dark">{{ project.Title }}</strong></u>
|
||||
</a>
|
||||
<div class="stars">
|
||||
{{ project.Languages.join(' | ') }}
|
||||
<ng-container *ngIf="project.Stars != null">
|
||||
<fa-icon [icon]="faStar"></fa-icon>
|
||||
{{ project.Stars }}
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
<p>{{ project.Description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<app-footer ngSkipHydration></app-footer>
|
||||
|
||||
<div *ngFor="let project of projects">
|
||||
<div *ngIf="project.IsReadmeShown" class="modal github-modal">
|
||||
<div class="modal github-modal card big-card">
|
||||
<a class="modalClose" (click)="project.IsReadmeShown = false">❌</a>
|
||||
<a href="{{project.Link}}">Visit on GitHub</a>
|
||||
<br>
|
||||
<br>
|
||||
<div [innerHTML]="project.Readme"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="isApiRateLimitExceeded" class="modal github-modal">
|
||||
<div class="modal github-modal card mini-card">
|
||||
<a class="modalClose" (click)="isApiRateLimitExceeded = false">❌</a>
|
||||
<br>
|
||||
<br>
|
||||
<h1 style="text-align: center">You have exceeded the today's github api request limits!</h1>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,23 @@
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {HomeComponent} from './home.component';
|
||||
|
||||
describe('HomeComponent', (): void => {
|
||||
let component: HomeComponent;
|
||||
let fixture: ComponentFixture<HomeComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [HomeComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(HomeComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', (): void => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,245 @@
|
||||
import {
|
||||
Component,
|
||||
NgZone,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
} from '@angular/core';
|
||||
import {trigger, transition, style, animate} from '@angular/animations';
|
||||
import {faArrowRight, faArrowLeft, faArrowDown, faStar, faEnvelope, faPhone} from '@fortawesome/free-solid-svg-icons';
|
||||
import {
|
||||
faDiscord,
|
||||
faGithub,
|
||||
faXing,
|
||||
faLinkedin
|
||||
} from '@fortawesome/free-brands-svg-icons';
|
||||
import {NgForOf, NgIf, NgOptimizedImage} from '@angular/common';
|
||||
import {FaIconComponent, IconDefinition} from '@fortawesome/angular-fontawesome';
|
||||
import {DomSanitizer, SafeUrl} from '@angular/platform-browser';
|
||||
import {interval, Subscription} from 'rxjs';
|
||||
import MarkdownIt from 'markdown-it';
|
||||
|
||||
import {globalFields} from '../../global.fields';
|
||||
import {FooterComponent} from '../footer/footer.component';
|
||||
import {MatDialog} from '@angular/material/dialog';
|
||||
import {StalkerComponent} from '../stalker/stalker.component';
|
||||
|
||||
interface Project {
|
||||
Link: string;
|
||||
Title: string;
|
||||
FullName?: string;
|
||||
Description: string;
|
||||
Languages: string[];
|
||||
DefaultBranch?: string;
|
||||
Stars?: number;
|
||||
IsReadmeShown: boolean;
|
||||
Readme?: string;
|
||||
Clickable?: boolean;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-home',
|
||||
standalone: true,
|
||||
imports: [
|
||||
NgOptimizedImage,
|
||||
FooterComponent,
|
||||
NgIf,
|
||||
FaIconComponent,
|
||||
NgForOf
|
||||
],
|
||||
templateUrl: './home.component.html',
|
||||
animations: [
|
||||
trigger('fadeInOut', [
|
||||
transition(':enter', [
|
||||
style({opacity: 0}),
|
||||
animate('300ms ease-in', style({opacity: 1}))
|
||||
]),
|
||||
transition(':leave', [
|
||||
animate('300ms ease-out', style({opacity: 0}))
|
||||
])
|
||||
])
|
||||
]
|
||||
})
|
||||
export class HomeComponent implements OnInit, OnDestroy {
|
||||
protected readonly globalFields: typeof globalFields = globalFields;
|
||||
|
||||
protected readonly age: number | string = this.calculateAge('2009-03-03');
|
||||
|
||||
protected isLongBioShown: boolean = false;
|
||||
protected isApiRateLimitExceeded: boolean = false;
|
||||
protected readonly faArrowRight: IconDefinition = faArrowRight;
|
||||
protected readonly faArrowLeft: IconDefinition = faArrowLeft;
|
||||
protected readonly faArrowDown: IconDefinition = faArrowDown;
|
||||
protected readonly faStar: IconDefinition = faStar;
|
||||
protected readonly faEnvelope: IconDefinition = faEnvelope;
|
||||
protected readonly faPhone: IconDefinition = faPhone;
|
||||
readonly faDiscord: IconDefinition = faDiscord;
|
||||
readonly faGithub: IconDefinition = faGithub;
|
||||
readonly faXing: IconDefinition = faXing;
|
||||
readonly faLinkedin: IconDefinition = faLinkedin;
|
||||
|
||||
protected readonly contactSafeMail: SafeUrl;
|
||||
|
||||
protected readonly personalInformation: string = 'You found an easter egg, why are you here!?!\nYou want to know more personal things about me? - Then you will find your information here!:';
|
||||
readonly bioTextsList: string[] = [
|
||||
'C#',
|
||||
'.NET',
|
||||
'.NET Framework',
|
||||
'WPF',
|
||||
'Avalonia',
|
||||
'NodeJS',
|
||||
'Angular',
|
||||
'TypeScript',
|
||||
'JavaScript',
|
||||
'Java',
|
||||
'HTML',
|
||||
'(S)CSS',
|
||||
'Github',
|
||||
'Gitlab',
|
||||
'Azure DevOps',
|
||||
'Ubuntu',
|
||||
'Debian',
|
||||
'SSMS',
|
||||
'MariaDB',
|
||||
'MySQL',
|
||||
'Grandle'
|
||||
];
|
||||
|
||||
protected currentIndex = 0;
|
||||
private sub!: Subscription;
|
||||
|
||||
protected projects: Project[] = [
|
||||
{
|
||||
Link: "https://www.synradio.de/",
|
||||
Title: "SynRadio",
|
||||
Description: "🖥️ An internet radio station that is available on various media.",
|
||||
Languages: ["TypeScript", "JavaScript", "HTML", "CSS"],
|
||||
Clickable: false,
|
||||
IsReadmeShown: false
|
||||
},
|
||||
{
|
||||
Link: "https://www.synhost.de/",
|
||||
Title: "SynHost",
|
||||
Description: "❓ A support system integrating various platforms and media to offer employees a unified overview.",
|
||||
Languages: ["TypeScript"],
|
||||
Clickable: false,
|
||||
IsReadmeShown: false
|
||||
}
|
||||
]
|
||||
|
||||
constructor(private _sanitizer: DomSanitizer, private zone: NgZone, private dialog: MatDialog) {
|
||||
this.fillProjects();
|
||||
this.contactSafeMail = this._sanitizer.bypassSecurityTrustUrl(`mailto:${globalFields.contactMail}`);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.zone.runOutsideAngular((): void => {
|
||||
this.sub = interval(1000).subscribe((): void => {
|
||||
this.zone.run((): void => {
|
||||
this.currentIndex = (this.currentIndex + 1) % this.bioTextsList.length;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.sub.unsubscribe();
|
||||
}
|
||||
openInfo(): void {
|
||||
this.dialog.open(StalkerComponent, {
|
||||
width: '420px',
|
||||
data: { message: this.personalInformation }
|
||||
});
|
||||
}
|
||||
|
||||
private fillProjects(): void {
|
||||
const sortOnKey: <T>(key: keyof T, descending: boolean) => (a: T, b: T) => number = <T>(key: keyof T, descending: boolean): (a: T, b: T) => number => {
|
||||
return (a: T, b: T): number => {
|
||||
const aValue = a[key] as number;
|
||||
const bValue = b[key] as number;
|
||||
return descending ? bValue - aValue : aValue - bValue;
|
||||
};
|
||||
};
|
||||
|
||||
fetch(`https://api.github.com/users/${this.globalFields.githubUsername}/repos?per_page=${this.globalFields.numberOfLoadedRepositories}`)
|
||||
.then(async (rawResponse: Response): Promise<any> => {
|
||||
if (rawResponse.status === 403) {
|
||||
const data = await rawResponse.json();
|
||||
if (data.message && data.documentation_url) {
|
||||
this.isApiRateLimitExceeded = true;
|
||||
}
|
||||
throw new Error('403 Forbidden');
|
||||
}
|
||||
return rawResponse.json();
|
||||
})
|
||||
.then((allProjects: any): void => {
|
||||
allProjects = allProjects.sort(sortOnKey("stargazers_count", true));
|
||||
allProjects.sort(sortOnKey("stargazers_count", true)).forEach((currentProject: any): void => {
|
||||
if (currentProject === undefined || currentProject.fork === true) return;
|
||||
if (currentProject.description == null) currentProject.description = "";
|
||||
if (currentProject.name === "FrauJulian") return;
|
||||
|
||||
let newProject: Project = {
|
||||
Link: "",
|
||||
Title: "",
|
||||
FullName: "",
|
||||
Description: "",
|
||||
Languages: [],
|
||||
DefaultBranch: "",
|
||||
Stars: 0,
|
||||
Clickable: true,
|
||||
IsReadmeShown: false,
|
||||
Readme: ""
|
||||
};
|
||||
|
||||
if (currentProject.language != null) {
|
||||
fetch(currentProject.languages_url)
|
||||
.then((rawResponse: Response): Promise<any> => rawResponse.json())
|
||||
.then((languages: any): void => {
|
||||
newProject.Languages = Object.keys(languages);
|
||||
});
|
||||
} else {
|
||||
newProject.Languages = [""];
|
||||
}
|
||||
|
||||
fetch(`https://raw.githubusercontent.com/${currentProject.full_name}/${currentProject.default_branch}/README.md`)
|
||||
.then((rawResponse: Response): Promise<any> => rawResponse.text())
|
||||
.then((readmeText: string): void => {
|
||||
newProject.Readme = new MarkdownIt({html: true, linkify: true, typographer: true}).render(readmeText);
|
||||
});
|
||||
|
||||
currentProject.name = currentProject.name
|
||||
.replaceAll("_", " ")
|
||||
.replaceAll("-", " ");
|
||||
|
||||
newProject.Link = currentProject.html_url;
|
||||
newProject.Title = currentProject.name;
|
||||
newProject.FullName = currentProject.full_name;
|
||||
newProject.Description = currentProject.description;
|
||||
newProject.DefaultBranch = currentProject.default_branch;
|
||||
newProject.Stars = currentProject.stargazers_count;
|
||||
newProject.IsReadmeShown = false;
|
||||
|
||||
this.projects.push(newProject);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected toggleBio(): void {
|
||||
this.isLongBioShown = !this.isLongBioShown;
|
||||
}
|
||||
|
||||
protected closeOnOverlayClick(evt: MouseEvent): void {
|
||||
if ((evt.target as HTMLElement).classList.contains('long-bio-overlay')) {
|
||||
this.isLongBioShown = false;
|
||||
}
|
||||
}
|
||||
|
||||
private calculateAge(birthDateString: string): number | string {
|
||||
let birthDate: Date = new Date(birthDateString);
|
||||
let now: Date = new Date();
|
||||
let diffMs: number = now.getTime() - birthDate.getTime();
|
||||
let diffYears: number = diffMs / (1000 * 60 * 60 * 24 * 365.25);
|
||||
let tempAge: string = diffYears.toFixed(1);
|
||||
return tempAge.endsWith('.0') ? Math.round(diffYears) : tempAge;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<div class="container">
|
||||
<div class="card profile">
|
||||
|
||||
<h1>Imprint</h1>
|
||||
|
||||
<h2>Information according to §5 ECG</h2>
|
||||
|
||||
{{ globalFields.firstname }} {{ globalFields.lastname }} <br>
|
||||
{{ street }} {{ houseNumber }} <br>
|
||||
{{ zip }} {{ city }} <br>
|
||||
{{ country }} <br>
|
||||
|
||||
<br>
|
||||
|
||||
<h4>Contact:</h4>
|
||||
Phone: <b><u><a href="tel:{{globalFields.hrefContactPhone}}">{{ globalFields.contactPhone }}</a></u></b> <br>
|
||||
E-Mail: <b><u><a [href]="contactSafeMail">{{ globalFields.contactMail }}</a></u></b>
|
||||
|
||||
<br>
|
||||
|
||||
<h4>Abuse Contact:</h4>
|
||||
E-Mail: <b><u><a [href]="abuseSafeMail">{{ abuseMail }}</a></u></b>
|
||||
|
||||
<br>
|
||||
|
||||
<h2>Disclaimer:</h2>
|
||||
|
||||
<h3>Liability for links</h3>
|
||||
Our website contains links to external third-party websites over whose content we have no influence. Therefore, we
|
||||
cannot accept any liability for this third-party content. The respective provider or operator of the pages is
|
||||
always responsible for the content of the linked pages. The linked pages were checked for possible legal
|
||||
violations at the time of linking. Illegal content was not recognizable at the time of linking. However, permanent
|
||||
monitoring of the content of the linked pages is not reasonable without concrete evidence of an infringement. If
|
||||
we become aware of any legal infringements, we will remove such links immediately.
|
||||
|
||||
<br>
|
||||
<br>
|
||||
|
||||
<h3>Copyright</h3>
|
||||
The content and works created by the site operators on these pages are subject to German copyright law.
|
||||
Duplication, processing, distribution and any form of commercialization of such material beyond the scope of the
|
||||
copyright law shall require the prior written consent of its respective author or creator. Downloads and copies of
|
||||
this site are only permitted for private, non-commercial use. Insofar as the content on this site was not created
|
||||
by the operator, the copyrights of third parties are respected. In particular, third-party content is identified
|
||||
as such. Should you nevertheless become aware of a copyright infringement, please inform us accordingly. If we
|
||||
become aware of any infringements, we will remove such content immediately.
|
||||
|
||||
<br>
|
||||
<br>
|
||||
|
||||
<h3>Data privacy</h3>
|
||||
The use of our website is generally possible without providing personal data. Insofar as personal data (e.g. name,
|
||||
address or e-mail addresses) is collected on our website, this is always done on a voluntary basis as far as
|
||||
possible. This data will not be passed on to third parties without your express consent.
|
||||
We would like to point out that data transmission over the Internet (e.g. when communicating by e-mail) may be
|
||||
subject to security vulnerabilities. Complete protection of data against access by third parties is not possible.
|
||||
We hereby expressly prohibit the use of contact data published within the scope of the imprint obligation by third
|
||||
parties for sending unsolicited advertising and information material. The operators of the website expressly
|
||||
reserve the right to take legal action in the event of the unsolicited sending of advertising information, such as
|
||||
spam e-mails.
|
||||
|
||||
<div class="max60">
|
||||
<div id="short-desc" class="hover-items" routerLink="">
|
||||
<fa-icon [icon]="faArrowRight"></fa-icon>
|
||||
<a>
|
||||
<u>
|
||||
<h2>Back</h2>
|
||||
</u>
|
||||
</a>
|
||||
<fa-icon [icon]="faArrowLeft"></fa-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<app-footer ngSkipHydration></app-footer>
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ImprintComponent } from './imprint.component';
|
||||
|
||||
describe('ImprintComponent', (): void => {
|
||||
let component: ImprintComponent;
|
||||
let fixture: ComponentFixture<ImprintComponent>;
|
||||
|
||||
beforeEach(async (): Promise<void> => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ImprintComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ImprintComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', (): void => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {FooterComponent} from '../footer/footer.component';
|
||||
import {RouterLink} from '@angular/router';
|
||||
import {globalFields} from '../../global.fields';
|
||||
import {DomSanitizer, SafeUrl} from '@angular/platform-browser';
|
||||
import {FaIconComponent, IconDefinition} from '@fortawesome/angular-fontawesome';
|
||||
import {faArrowDown, faArrowLeft, faArrowRight} from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
@Component({
|
||||
selector: 'app-imprint',
|
||||
imports: [
|
||||
FooterComponent,
|
||||
FaIconComponent,
|
||||
RouterLink
|
||||
],
|
||||
templateUrl: './imprint.component.html'
|
||||
})
|
||||
export class ImprintComponent implements OnInit {
|
||||
protected readonly globalFields: typeof globalFields = globalFields;
|
||||
|
||||
protected readonly street: string = 'Ulmenstraße';
|
||||
protected readonly houseNumber: number = 9;
|
||||
protected readonly zip: number = 3380;
|
||||
protected readonly city: string = 'Pöchlarn';
|
||||
protected readonly country: string = 'Austria';
|
||||
protected readonly abuseMail: string = 'abuse@fraujulian.xyz';
|
||||
|
||||
protected contactSafeMail!: SafeUrl;
|
||||
protected abuseSafeMail!: SafeUrl;
|
||||
|
||||
protected readonly faArrowRight: IconDefinition = faArrowRight;
|
||||
protected readonly faArrowLeft: IconDefinition = faArrowLeft;
|
||||
|
||||
constructor(private _sanitizer: DomSanitizer) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.contactSafeMail = this._sanitizer.bypassSecurityTrustUrl(`mailto:${globalFields.contactMail}`);
|
||||
this.abuseSafeMail = this._sanitizer.bypassSecurityTrustUrl(`mailto:${this.abuseMail}`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<h1 mat-dialog-title>STALKER!!!</h1>
|
||||
|
||||
<div mat-dialog-content *ngFor="let line of lines">
|
||||
{{ line }} <br>
|
||||
</div>
|
||||
|
||||
<div mat-dialog-actions>
|
||||
<button mat-button mat-dialog-close>Close</button>
|
||||
</div>
|
||||
@@ -0,0 +1,23 @@
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {StalkerComponent} from './stalker.component';
|
||||
|
||||
describe('StalkerComponent', (): void => {
|
||||
let component: StalkerComponent;
|
||||
let fixture: ComponentFixture<StalkerComponent>;
|
||||
|
||||
beforeEach(async (): Promise<void> => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [StalkerComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(StalkerComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', (): void => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
import {Component, Inject} from '@angular/core';
|
||||
import {
|
||||
MAT_DIALOG_DATA,
|
||||
MatDialogActions,
|
||||
MatDialogClose,
|
||||
MatDialogContent,
|
||||
MatDialogTitle
|
||||
} from '@angular/material/dialog';
|
||||
import {MatButton} from '@angular/material/button';
|
||||
import {NgForOf, CommonModule} from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'app-stalker',
|
||||
imports: [
|
||||
MatDialogTitle,
|
||||
MatDialogActions,
|
||||
MatDialogContent,
|
||||
MatButton,
|
||||
MatDialogClose,
|
||||
CommonModule
|
||||
],
|
||||
templateUrl: './stalker.component.html'
|
||||
})
|
||||
export class StalkerComponent {
|
||||
lines: string[];
|
||||
|
||||
constructor(@Inject(MAT_DIALOG_DATA) public data: { message: string }) {
|
||||
this.lines = (data.message ?? '').split('\n');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export class globalFields {
|
||||
public static readonly firstname: string = 'Julian';
|
||||
public static readonly lastname: string = 'Lechner';
|
||||
public static readonly contactMail: string = 'contact@fraujulian.xyz';
|
||||
public static readonly contactPhone: string = '+43 (0) 660 9254001';
|
||||
public static readonly hrefContactPhone: string = 'contact@fraujulian.xyz';
|
||||
public static readonly githubUsername: string = 'FrauJulian';
|
||||
public static readonly numberOfLoadedRepositories: number = 999;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta charset="utf-8">
|
||||
<base href="/">
|
||||
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<title>Lechner Julian</title>
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,9 @@
|
||||
import {bootstrapApplication} from '@angular/platform-browser';
|
||||
import {ApplicationRef} from '@angular/core';
|
||||
|
||||
import {AppComponent} from './app/app.component';
|
||||
import {config} from './app/app.config.server';
|
||||
|
||||
const bootstrap: () => Promise<ApplicationRef> = (): Promise<ApplicationRef> => bootstrapApplication(AppComponent, config);
|
||||
|
||||
export default bootstrap;
|
||||
@@ -0,0 +1,7 @@
|
||||
import {bootstrapApplication} from '@angular/platform-browser';
|
||||
|
||||
import {appConfig} from './app/app.config';
|
||||
import {AppComponent} from './app/app.component';
|
||||
|
||||
bootstrapApplication(AppComponent, appConfig)
|
||||
.catch((err: Error): void => console.error(err.stack));
|
||||
@@ -0,0 +1,48 @@
|
||||
import {APP_BASE_HREF} from '@angular/common';
|
||||
import {CommonEngine, isMainModule} from '@angular/ssr/node';
|
||||
import express, {Express, NextFunction, Request, Response} from 'express';
|
||||
import {dirname, join, resolve} from 'node:path';
|
||||
import {fileURLToPath} from 'node:url';
|
||||
|
||||
import bootstrap from './main.server';
|
||||
import ProductionConfig from '../configs/Production.json';
|
||||
|
||||
const serverDistFolder: string = dirname(fileURLToPath(import.meta.url));
|
||||
const browserDistFolder: string = resolve(serverDistFolder, '../browser');
|
||||
const indexHtml: string = join(serverDistFolder, 'index.server.html');
|
||||
|
||||
const app: Express = express();
|
||||
const commonEngine: CommonEngine = new CommonEngine();
|
||||
|
||||
app.get(
|
||||
'**',
|
||||
express.static(browserDistFolder, {
|
||||
maxAge: '1y',
|
||||
index: 'index.html'
|
||||
}),
|
||||
);
|
||||
|
||||
app.get('**', (req: Request, res: Response, next: NextFunction): void => {
|
||||
const {protocol, originalUrl, baseUrl, headers} = req;
|
||||
|
||||
commonEngine
|
||||
.render({
|
||||
bootstrap,
|
||||
documentFilePath: indexHtml,
|
||||
url: `${protocol}://${headers.host}${originalUrl}`,
|
||||
publicPath: browserDistFolder,
|
||||
providers: [{provide: APP_BASE_HREF, useValue: baseUrl}],
|
||||
})
|
||||
.then((html: string): Response => res.send(html))
|
||||
.catch((err: Error): void => next(err.stack));
|
||||
});
|
||||
|
||||
if (isMainModule(import.meta.url)) {
|
||||
const host: string = ProductionConfig.Server.Host;
|
||||
const port: number = ProductionConfig.Server.Port;
|
||||
app.listen(port, host, (): void => {
|
||||
console.log(`✔️ Listening on http://${host}:${port}`);
|
||||
});
|
||||
}
|
||||
|
||||
export default app;
|
||||
+573
@@ -0,0 +1,573 @@
|
||||
::-webkit-scrollbar {
|
||||
width: var(--scroll-width);
|
||||
height: var(--scroll-width);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--scroll-thumb-bg);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--scroll-track-bg);
|
||||
}
|
||||
|
||||
:root {
|
||||
--main-padding: 15px;
|
||||
--main-margin: 10px;
|
||||
--main-img-size: 200px;
|
||||
--main-bio-size: 200px;
|
||||
--main-bio-margin: 5px;
|
||||
--main-icon-width: 80px;
|
||||
--main-border-radius: 5px;
|
||||
--padding-big: 20px;
|
||||
--padding-small: 5px;
|
||||
--main-container-width: 750px;
|
||||
--main-txt-color: White;
|
||||
--main-shadow-color: #6F3E38;
|
||||
--donation-modal-height: 200px;
|
||||
--donation-modal-width: 600px;
|
||||
--donation-modal-top: 50%;
|
||||
--scroll-width: 6px;
|
||||
--scroll-thumb-bg: Red;
|
||||
--scroll-track-bg: transparent;
|
||||
--background-image: url("../public/Background.png");
|
||||
--blur-small: 25px;
|
||||
--blur-large: 180px;
|
||||
--shadow-y: 4px;
|
||||
--shadow-blur: 8px;
|
||||
--shadow-spread: 0;
|
||||
--shadow-hover-y: 8px;
|
||||
--shadow-hover-blur: 16px;
|
||||
--font-size-large: 20px;
|
||||
--width-medium: 60%;
|
||||
--max-width-large: 90%;
|
||||
--grid-column-width: 24%;
|
||||
--portfolio-item-height: 128px;
|
||||
--transition-duration-long: 0.5s;
|
||||
--transition-duration-short: 0.3s;
|
||||
--modal-card-height: 80%;
|
||||
--modal-mini-card-height: 22%;
|
||||
}
|
||||
|
||||
* {
|
||||
font-family: 'Outfit', Arial, sans-serif !important;
|
||||
}
|
||||
|
||||
a,
|
||||
hr {
|
||||
color: var(--main-txt-color);
|
||||
text-decoration-line: none;
|
||||
}
|
||||
|
||||
body,
|
||||
html {
|
||||
margin: 0;
|
||||
color: var(--main-txt-color);
|
||||
text-shadow: 0 0 5px var(--main-shadow-color);
|
||||
text-align: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-attachment: fixed;
|
||||
background-image: var(--background-image);
|
||||
background-position: center center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
footer {
|
||||
padding-bottom: var(--main-padding);
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
footer a {
|
||||
color: var(--main-txt-color);
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
|
||||
h2 {
|
||||
line-height-step: 100px;
|
||||
}
|
||||
|
||||
ul.nav {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
backdrop-filter: blur(var(--blur-large));
|
||||
box-shadow: 0 var(--shadow-y) var(--shadow-blur) var(--shadow-spread) var(--main-shadow-color);
|
||||
transition: var(--transition-duration-short);
|
||||
}
|
||||
|
||||
ul.nav li {
|
||||
float: right;
|
||||
}
|
||||
|
||||
ul.nav li a {
|
||||
display: block;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
ul.nav li a.active {
|
||||
background-color: #2f3136;
|
||||
}
|
||||
|
||||
ul.nav li a:hover:not(.active) {
|
||||
background-color: #111;
|
||||
}
|
||||
|
||||
ul.nav li.left {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.modalClose {
|
||||
float: right;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#bio {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
|
||||
#bio:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#donate {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
|
||||
#donate:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#donateModal {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#github_projects .card {
|
||||
padding: var(--padding-big);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#github_projects .card a {
|
||||
color: var(--main-txt-color) !important;
|
||||
}
|
||||
|
||||
#github_projects .card p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#github_projects .stargazers {
|
||||
padding-right: var(--padding-small);
|
||||
}
|
||||
|
||||
#github_projects .stars {
|
||||
color: var(--main-txt-color) !important;
|
||||
float: right;
|
||||
}
|
||||
|
||||
#github_projects a {
|
||||
text-transform: capitalize;
|
||||
text-decoration-line: none;
|
||||
}
|
||||
|
||||
#left-arrow {
|
||||
position: relative;
|
||||
-webkit-animation: linear infinite alternate;
|
||||
-webkit-animation-name: runLeft;
|
||||
-webkit-animation-duration: 2s;
|
||||
}
|
||||
|
||||
#right-arrow {
|
||||
position: relative;
|
||||
-webkit-animation: linear infinite alternate;
|
||||
-webkit-animation-name: runRight;
|
||||
-webkit-animation-duration: 2s;
|
||||
}
|
||||
|
||||
.bio {
|
||||
display: grid !important;
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
padding: var(--main-padding);
|
||||
}
|
||||
|
||||
.bio table .right {
|
||||
cursor: pointer;
|
||||
border-radius: var(--main-border-radius);
|
||||
backdrop-filter: blur(150px);
|
||||
}
|
||||
|
||||
.long-bio-text {
|
||||
font-size: var(--font-size-large);
|
||||
width: var(--width-medium);
|
||||
margin-bottom: var(--main-padding) !important;
|
||||
margin: auto;
|
||||
padding: var(--main-margin);
|
||||
}
|
||||
|
||||
.card {
|
||||
margin-top: var(--main-padding);
|
||||
box-shadow: 0 var(--shadow-y) var(--shadow-blur) var(--shadow-spread) var(--main-shadow-color);
|
||||
transition: var(--transition-duration-short);
|
||||
backdrop-filter: blur(var(--blur-small));
|
||||
padding: var(--main-padding);
|
||||
text-align: center;
|
||||
color: var(--main-txt-color);
|
||||
}
|
||||
|
||||
.card-projects {
|
||||
display: grid;
|
||||
box-shadow: 0 var(--shadow-y) var(--shadow-blur) var(--shadow-spread) var(--main-shadow-color);
|
||||
transition: var(--transition-duration-short);
|
||||
backdrop-filter: blur(var(--blur-large));
|
||||
}
|
||||
|
||||
.card-projects:hover {
|
||||
box-shadow: 0 var(--shadow-hover-y) var(--shadow-hover-blur) var(--shadow-spread) var(--main-shadow-color);
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
box-shadow: 0 var(--shadow-hover-y) var(--shadow-hover-blur) var(--shadow-spread) var(--main-shadow-color);
|
||||
}
|
||||
|
||||
.container {
|
||||
background: transparent;
|
||||
backdrop-filter: blur(var(--blur-small));
|
||||
margin: auto;
|
||||
width: var(--main-container-width);
|
||||
}
|
||||
|
||||
.github-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.github-modal img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.github-modal > .card {
|
||||
overflow-y: auto;
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.big-card {
|
||||
height: var(--modal-card-height) !important;
|
||||
}
|
||||
|
||||
.mini-card {
|
||||
height: var(--modal-mini-card-height) !important;
|
||||
}
|
||||
|
||||
.grid-container {
|
||||
margin: var(--main-margin);
|
||||
display: grid;
|
||||
justify-content: center;
|
||||
grid-template-columns: auto auto;
|
||||
}
|
||||
|
||||
.grid-item {
|
||||
width: var(--main-bio-size);
|
||||
margin-left: var(--main-padding);
|
||||
backdrop-filter: blur(var(--blur-large));
|
||||
border-radius: var(--main-border-radius);
|
||||
}
|
||||
|
||||
.grid-item h1,
|
||||
.grid-item-start h1 {
|
||||
margin: var(--main-bio-margin);
|
||||
}
|
||||
|
||||
.grid-item-start {
|
||||
grid-row-start: 1;
|
||||
grid-row-end: 8;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hover-items {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto auto;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.hover-items a:hover {
|
||||
cursor: pointer;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.hover-items,
|
||||
.hover-items a {
|
||||
color: var(--main-txt-color);
|
||||
padding: var(--main-padding) var(--main-margin);
|
||||
font-size: large;
|
||||
margin: auto;
|
||||
line-height: 1.5em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hoverable:hover {
|
||||
backdrop-filter: blur(var(--blur-large));
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.fab {
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
display: inline-block;
|
||||
font-style: normal;
|
||||
font-variant: normal;
|
||||
line-height: 1;
|
||||
text-rendering: auto;
|
||||
}
|
||||
|
||||
.contact-area a {
|
||||
color: var(--main-txt-color);
|
||||
}
|
||||
|
||||
.contact-area fa-icon {
|
||||
justify-content: space-between;
|
||||
width: var(--main-icon-width);
|
||||
padding-top: var(--main-padding);
|
||||
padding-bottom: var(--main-padding);
|
||||
border-radius: var(--main-border-radius);
|
||||
}
|
||||
|
||||
.contact-area fa-icon:hover {
|
||||
cursor: pointer;
|
||||
backdrop-filter: blur(var(--blur-large));
|
||||
}
|
||||
|
||||
.max60 {
|
||||
max-width: var(--max-width-large);
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.media-body {
|
||||
color: var(--main-txt-color) !important;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.modal {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.modal .card {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
height: var(--donation-modal-height);
|
||||
width: var(--donation-modal-width);
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.nomargin {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.portfolio {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, 128px);
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.portfolio-done {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, var(--grid-column-width));
|
||||
justify-content: space-between;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
.portfolio-done-item {
|
||||
height: var(--portfolio-item-height);
|
||||
}
|
||||
|
||||
.profile img {
|
||||
margin: auto;
|
||||
max-width: var(--main-img-size);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.project {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.project img {
|
||||
opacity: 1;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
transition: var(--transition-duration-long) ease;
|
||||
backface-visibility: hidden;
|
||||
box-shadow: 0 var(--shadow-y) var(--shadow-blur) var(--shadow-spread) var(--main-shadow-color);
|
||||
transition: var(--transition-duration-short);
|
||||
}
|
||||
|
||||
.project p {
|
||||
transition: var(--transition-duration-long) ease;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
-ms-transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.project:hover img {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.project:hover p {
|
||||
opacity: 1.0;
|
||||
}
|
||||
|
||||
.refer-area {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
|
||||
.social {
|
||||
border: 0;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.social a {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.table {
|
||||
color: var(--main-txt-color) !important;
|
||||
font-weight: lighter;
|
||||
}
|
||||
|
||||
@-webkit-keyframes runLeft {
|
||||
0% {
|
||||
left: 50%;
|
||||
}
|
||||
|
||||
100% {
|
||||
left: 50%;
|
||||
}
|
||||
|
||||
50% {
|
||||
left: 70%;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes runRight {
|
||||
0% {
|
||||
right: 50%;
|
||||
}
|
||||
|
||||
100% {
|
||||
right: 50%;
|
||||
}
|
||||
|
||||
50% {
|
||||
right: 70%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1025px) {
|
||||
.circle {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
border-radius: 24px;
|
||||
background-color: black;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
pointer-events: none;
|
||||
z-index: 99999999;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 700px) {
|
||||
:root {
|
||||
--donation-modal-height: 350px;
|
||||
--donation-modal-width: 600px;
|
||||
--donation-modal-top: 50%;
|
||||
--mobile-margin: 5px;
|
||||
--main-container-width: 95%;
|
||||
--mobile-width: 90%;
|
||||
}
|
||||
|
||||
body,
|
||||
html {
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
.modal {
|
||||
position: absolute;
|
||||
margin: auto;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.modal .card {
|
||||
vertical-align: middle;
|
||||
width: 80%;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 700px) {
|
||||
footer {
|
||||
padding-bottom: var(--main-padding);
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
ul.nav li,
|
||||
ul.nav li.right {
|
||||
float: none;
|
||||
}
|
||||
|
||||
ul.nav li.left {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#left-arrow {
|
||||
position: initial !important;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
#right-arrow {
|
||||
position: initial !important;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.long-bio-text {
|
||||
width: 95% !important;
|
||||
}
|
||||
|
||||
.grid-container {
|
||||
grid-template-columns: auto;
|
||||
}
|
||||
|
||||
.contact-area i {
|
||||
margin-top: var(--mobile-margin);
|
||||
}
|
||||
|
||||
.max60 {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.portfolio-done {
|
||||
grid-template-columns: auto;
|
||||
justify-content: initial;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.portfolio-done-item {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.short_desc {
|
||||
width: var(--mobile-width);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user