@@ -26,13 +26,8 @@
|
|||||||
"assets": [
|
"assets": [
|
||||||
{
|
{
|
||||||
"glob": "**/*",
|
"glob": "**/*",
|
||||||
"input": "src/assets/optimized",
|
"input": "src/assets",
|
||||||
"output": "assets/optimized"
|
"output": "assets"
|
||||||
},
|
|
||||||
{
|
|
||||||
"glob": "**/*",
|
|
||||||
"input": "src/assets/logos",
|
|
||||||
"output": "assets/logos"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"glob": "**/*",
|
"glob": "**/*",
|
||||||
@@ -113,13 +108,8 @@
|
|||||||
"assets": [
|
"assets": [
|
||||||
{
|
{
|
||||||
"glob": "**/*",
|
"glob": "**/*",
|
||||||
"input": "src/assets/optimized",
|
"input": "src/assets",
|
||||||
"output": "assets/optimized"
|
"output": "assets"
|
||||||
},
|
|
||||||
{
|
|
||||||
"glob": "**/*",
|
|
||||||
"input": "src/assets/logos",
|
|
||||||
"output": "assets/logos"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"glob": "**/*",
|
"glob": "**/*",
|
||||||
|
|||||||
@@ -24,11 +24,10 @@
|
|||||||
"check": "npm run build && npm run lint && npm run format:check && npm run version:check && npm run text:check",
|
"check": "npm run build && npm run lint && npm run format:check && npm run version:check && npm run text:check",
|
||||||
"fix": "npm run version:place && npm run lint:fix && npm run text:fix && npm run format",
|
"fix": "npm run version:place && npm run lint:fix && npm run text:fix && npm run format",
|
||||||
"------ BUILD": "",
|
"------ BUILD": "",
|
||||||
"clean": "del-cli dist",
|
|
||||||
"generate:static-files": "node scripts/generate-static-files.cjs",
|
"generate:static-files": "node scripts/generate-static-files.cjs",
|
||||||
"generate:assets": "npm run generate:static-files",
|
"generate:assets": "npm run generate:static-files",
|
||||||
"compress:assets": "node scripts/compress-assets.cjs",
|
"compress:assets": "node scripts/compress-assets.cjs",
|
||||||
"build": "npm run clean && npm run generate:assets && ng build --configuration production && npm run compress:assets && node scripts/copy-static-root-files.cjs",
|
"build": "npm run generate:assets && ng build --configuration production && npm run compress:assets && node scripts/copy-static-root-files.cjs",
|
||||||
"------ STARTUP": "",
|
"------ STARTUP": "",
|
||||||
"test": "ng test",
|
"test": "ng test",
|
||||||
"dev": "npm run generate:assets && ng serve",
|
"dev": "npm run generate:assets && ng serve",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { ApplicationConfig } from '@angular/core';
|
import type { ApplicationConfig } from '@angular/core';
|
||||||
import { provideZoneChangeDetection } from '@angular/core';
|
import { importProvidersFrom, provideZoneChangeDetection } from '@angular/core';
|
||||||
import { provideRouter, withInMemoryScrolling } from '@angular/router';
|
import { provideRouter, withInMemoryScrolling } from '@angular/router';
|
||||||
import { provideClientHydration, withEventReplay } from '@angular/platform-browser';
|
import { BrowserModule, provideClientHydration, withEventReplay } from '@angular/platform-browser';
|
||||||
|
|
||||||
import { routes } from './app.routes';
|
import { routes } from './app.routes';
|
||||||
|
|
||||||
@@ -16,5 +16,6 @@ export const appConfig: ApplicationConfig = {
|
|||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
provideClientHydration(withEventReplay()),
|
provideClientHydration(withEventReplay()),
|
||||||
|
importProvidersFrom(BrowserModule),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,17 +1,10 @@
|
|||||||
import type { Routes } from '@angular/router';
|
import type { Routes } from '@angular/router';
|
||||||
|
|
||||||
|
import { HomeComponent } from './home/home.component';
|
||||||
|
import { ImprintComponent } from './imprint/imprint.component';
|
||||||
|
|
||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
{
|
{ path: '', component: HomeComponent, pathMatch: 'full' },
|
||||||
path: '',
|
{ path: 'imprint', component: ImprintComponent, pathMatch: 'full' },
|
||||||
loadComponent: async () =>
|
|
||||||
(await import('./home/home.component')).HomeComponent,
|
|
||||||
pathMatch: 'full',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'imprint',
|
|
||||||
loadComponent: async () =>
|
|
||||||
(await import('./imprint/imprint.component')).ImprintComponent,
|
|
||||||
pathMatch: 'full',
|
|
||||||
},
|
|
||||||
{ path: '**', redirectTo: '' },
|
{ path: '**', redirectTo: '' },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -64,28 +64,13 @@
|
|||||||
[title]="portraitHighlights.length > 1 ? 'Show next image' : 'Single image'"
|
[title]="portraitHighlights.length > 1 ? 'Show next image' : 'Single image'"
|
||||||
>
|
>
|
||||||
<div class="portrait-media" [class.portrait-media-switching]="isPortraitSwitching">
|
<div class="portrait-media" [class.portrait-media-switching]="isPortraitSwitching">
|
||||||
<picture>
|
|
||||||
<source
|
|
||||||
type="image/avif"
|
|
||||||
[attr.srcset]="currentPortraitHighlight.avifSrcset"
|
|
||||||
sizes="(max-width: 640px) calc(100vw - 2rem), (max-width: 900px) min(560px, calc(100vw - 2rem)), 560px"
|
|
||||||
/>
|
|
||||||
<source
|
|
||||||
type="image/webp"
|
|
||||||
[attr.srcset]="currentPortraitHighlight.webpSrcset"
|
|
||||||
sizes="(max-width: 640px) calc(100vw - 2rem), (max-width: 900px) min(560px, calc(100vw - 2rem)), 560px"
|
|
||||||
/>
|
|
||||||
<img
|
<img
|
||||||
class="hero-feature-image"
|
class="hero-feature-image"
|
||||||
[src]="currentPortraitHighlight.image"
|
[src]="currentPortraitHighlight.image"
|
||||||
[attr.width]="currentPortraitHighlight.width"
|
width="560"
|
||||||
[attr.height]="currentPortraitHighlight.height"
|
height="420"
|
||||||
[alt]="currentPortraitHighlight.alt"
|
[alt]="global.firstname + ' highlight'"
|
||||||
fetchpriority="high"
|
|
||||||
loading="eager"
|
|
||||||
decoding="async"
|
|
||||||
/>
|
/>
|
||||||
</picture>
|
|
||||||
@if (currentPortraitHighlightIndex === 0) {
|
@if (currentPortraitHighlightIndex === 0) {
|
||||||
<span class="portrait-click-hint">Click me!</span>
|
<span class="portrait-click-hint">Click me!</span>
|
||||||
}
|
}
|
||||||
@@ -163,11 +148,7 @@
|
|||||||
class="project-icon"
|
class="project-icon"
|
||||||
[class.project-icon-circle]="project.CircleIcon !== false"
|
[class.project-icon-circle]="project.CircleIcon !== false"
|
||||||
[src]="project.icon"
|
[src]="project.icon"
|
||||||
[attr.width]="project.iconWidth ?? 108"
|
|
||||||
[attr.height]="project.iconHeight ?? 108"
|
|
||||||
[alt]="project.title + ' icon'"
|
[alt]="project.title + ' icon'"
|
||||||
loading="lazy"
|
|
||||||
decoding="async"
|
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import { FooterComponent } from '../footer/footer.component';
|
|||||||
export class HomeComponent implements OnInit, AfterViewInit, OnDestroy {
|
export class HomeComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
private readonly sanitizer = inject(DomSanitizer);
|
private readonly sanitizer = inject(DomSanitizer);
|
||||||
private readonly zone = inject(NgZone);
|
private readonly zone = inject(NgZone);
|
||||||
|
private preloadedImages: HTMLImageElement[] = [];
|
||||||
private tallestPortraitAspectRatio: number | null = null;
|
private tallestPortraitAspectRatio: number | null = null;
|
||||||
private lockedHeroSectionMinHeight: number = 0;
|
private lockedHeroSectionMinHeight: number = 0;
|
||||||
private projectEntryObserver: IntersectionObserver | null = null;
|
private projectEntryObserver: IntersectionObserver | null = null;
|
||||||
@@ -81,7 +82,7 @@ export class HomeComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.initPortraitAspectRatio();
|
this.preloadImageAssets();
|
||||||
this.initNameGradientScrollAnimation();
|
this.initNameGradientScrollAnimation();
|
||||||
|
|
||||||
this.zone.runOutsideAngular((): void => {
|
this.zone.runOutsideAngular((): void => {
|
||||||
@@ -180,14 +181,61 @@ export class HomeComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
this.scrollToElementById('contact-links', this.getResponsiveScrollOffset(0.05));
|
this.scrollToElementById('contact-links', this.getResponsiveScrollOffset(0.05));
|
||||||
}
|
}
|
||||||
|
|
||||||
private initPortraitAspectRatio(): void {
|
private preloadImageAssets(): void {
|
||||||
const minAspectRatio: number = this.portraitHighlights.reduce(
|
if (typeof window === 'undefined') {
|
||||||
(minRatio: number, highlight: PortraitHighlight): number => {
|
return;
|
||||||
if (highlight.width <= 0 || highlight.height <= 0) {
|
}
|
||||||
|
|
||||||
|
const projectIcons: string[] = this.projects
|
||||||
|
.map((project: PortfolioProject): string | undefined => project.icon)
|
||||||
|
.filter((icon: string | undefined): icon is string => Boolean(icon));
|
||||||
|
|
||||||
|
const uniqueImageUrls: string[] = Array.from(
|
||||||
|
new Set<string>([
|
||||||
|
...this.portraitHighlights.map((highlight: PortraitHighlight): string => highlight.image),
|
||||||
|
...projectIcons,
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.preloadedImages = uniqueImageUrls.map((url: string): HTMLImageElement => {
|
||||||
|
const image = new Image();
|
||||||
|
image.decoding = 'async';
|
||||||
|
image.loading = 'eager';
|
||||||
|
image.src = url;
|
||||||
|
return image;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.syncHeroHeightToLargestPortraitImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
private syncHeroHeightToLargestPortraitImage(): void {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const portraitImageUrls: Set<string> = new Set<string>(
|
||||||
|
this.portraitHighlights.map(
|
||||||
|
(highlight: PortraitHighlight): string =>
|
||||||
|
new URL(highlight.image, window.location.href).href,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const portraitImages: HTMLImageElement[] = this.preloadedImages.filter(
|
||||||
|
(image: HTMLImageElement): boolean => portraitImageUrls.has(image.src),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (portraitImages.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const finalizeAspectRatio = (): void => {
|
||||||
|
const minAspectRatio: number = portraitImages.reduce(
|
||||||
|
(minRatio: number, image: HTMLImageElement): number => {
|
||||||
|
if (image.naturalWidth === 0 || image.naturalHeight === 0) {
|
||||||
return minRatio;
|
return minRatio;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentRatio: number = highlight.width / highlight.height;
|
const currentRatio: number = image.naturalWidth / image.naturalHeight;
|
||||||
return minRatio === 0 ? currentRatio : Math.min(minRatio, currentRatio);
|
return minRatio === 0 ? currentRatio : Math.min(minRatio, currentRatio);
|
||||||
},
|
},
|
||||||
0,
|
0,
|
||||||
@@ -195,6 +243,26 @@ export class HomeComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
|
|
||||||
if (minAspectRatio > 0) {
|
if (minAspectRatio > 0) {
|
||||||
this.tallestPortraitAspectRatio = minAspectRatio;
|
this.tallestPortraitAspectRatio = minAspectRatio;
|
||||||
|
this.scheduleHeroSectionHeightUpdate();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let pendingLoads: number = portraitImages.length;
|
||||||
|
const onImageSettled = (): void => {
|
||||||
|
pendingLoads -= 1;
|
||||||
|
if (pendingLoads === 0) {
|
||||||
|
finalizeAspectRatio();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const image of portraitImages) {
|
||||||
|
if (image.complete) {
|
||||||
|
onImageSettled();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
image.addEventListener('load', onImageSettled, { once: true });
|
||||||
|
image.addEventListener('error', onImageSettled, { once: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 182 KiB |
|
Before Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 143 KiB |
|
Before Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 142 KiB |
|
Before Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 27 KiB |
@@ -4,18 +4,11 @@ export interface PortfolioProject {
|
|||||||
description: string;
|
description: string;
|
||||||
languages: string[];
|
languages: string[];
|
||||||
icon?: string;
|
icon?: string;
|
||||||
iconWidth?: number;
|
|
||||||
iconHeight?: number;
|
|
||||||
CircleIcon?: boolean;
|
CircleIcon?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PortraitHighlight {
|
export interface PortraitHighlight {
|
||||||
image: string;
|
image: string;
|
||||||
avifSrcset: string;
|
|
||||||
webpSrcset: string;
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
alt: string;
|
|
||||||
text: string;
|
text: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,69 +74,27 @@ export const global: Global = {
|
|||||||
],
|
],
|
||||||
portraitHighlights: [
|
portraitHighlights: [
|
||||||
{
|
{
|
||||||
image: 'assets/optimized/portrait/me-960.jpg',
|
image: 'assets/portrait/me.png',
|
||||||
avifSrcset:
|
|
||||||
'assets/optimized/portrait/me-480.avif 480w, assets/optimized/portrait/me-720.avif 720w, assets/optimized/portrait/me-960.avif 900w',
|
|
||||||
webpSrcset:
|
|
||||||
'assets/optimized/portrait/me-480.webp 480w, assets/optimized/portrait/me-720.webp 720w, assets/optimized/portrait/me-960.webp 900w',
|
|
||||||
width: 900,
|
|
||||||
height: 1154,
|
|
||||||
alt: 'Portrait of Julian Lechner',
|
|
||||||
text: 'ME',
|
text: 'ME',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
image: 'assets/optimized/portrait/love-960.jpg',
|
image: 'assets/portrait/love.jpg',
|
||||||
avifSrcset:
|
|
||||||
'assets/optimized/portrait/love-480.avif 480w, assets/optimized/portrait/love-720.avif 720w, assets/optimized/portrait/love-960.avif 960w',
|
|
||||||
webpSrcset:
|
|
||||||
'assets/optimized/portrait/love-480.webp 480w, assets/optimized/portrait/love-720.webp 720w, assets/optimized/portrait/love-960.webp 960w',
|
|
||||||
width: 960,
|
|
||||||
height: 1065,
|
|
||||||
alt: 'Julian with his partner',
|
|
||||||
text: 'My Love',
|
text: 'My Love',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
image: 'assets/optimized/portrait/scuba-960.jpg',
|
image: 'assets/portrait/scuba.jpg',
|
||||||
avifSrcset:
|
|
||||||
'assets/optimized/portrait/scuba-480.avif 480w, assets/optimized/portrait/scuba-720.avif 720w, assets/optimized/portrait/scuba-960.avif 960w',
|
|
||||||
webpSrcset:
|
|
||||||
'assets/optimized/portrait/scuba-480.webp 480w, assets/optimized/portrait/scuba-720.webp 720w, assets/optimized/portrait/scuba-960.webp 960w',
|
|
||||||
width: 960,
|
|
||||||
height: 720,
|
|
||||||
alt: 'Julian scuba diving',
|
|
||||||
text: 'Scuba Diving',
|
text: 'Scuba Diving',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
image: 'assets/optimized/portrait/trains-960.jpg',
|
image: 'assets/portrait/trains.jpg',
|
||||||
avifSrcset:
|
|
||||||
'assets/optimized/portrait/trains-480.avif 480w, assets/optimized/portrait/trains-720.avif 720w, assets/optimized/portrait/trains-960.avif 960w',
|
|
||||||
webpSrcset:
|
|
||||||
'assets/optimized/portrait/trains-480.webp 480w, assets/optimized/portrait/trains-720.webp 720w, assets/optimized/portrait/trains-960.webp 960w',
|
|
||||||
width: 960,
|
|
||||||
height: 1275,
|
|
||||||
alt: 'Julian traveling by train',
|
|
||||||
text: 'Trains',
|
text: 'Trains',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
image: 'assets/optimized/portrait/traveling-960.jpg',
|
image: 'assets/portrait/traveling.jpg',
|
||||||
avifSrcset:
|
|
||||||
'assets/optimized/portrait/traveling-480.avif 480w, assets/optimized/portrait/traveling-720.avif 720w, assets/optimized/portrait/traveling-960.avif 960w',
|
|
||||||
webpSrcset:
|
|
||||||
'assets/optimized/portrait/traveling-480.webp 480w, assets/optimized/portrait/traveling-720.webp 720w, assets/optimized/portrait/traveling-960.webp 960w',
|
|
||||||
width: 960,
|
|
||||||
height: 720,
|
|
||||||
alt: 'Julian traveling',
|
|
||||||
text: 'Traveling',
|
text: 'Traveling',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
image: 'assets/optimized/portrait/culture-960.jpg',
|
image: 'assets/portrait/culture.jpg',
|
||||||
avifSrcset:
|
|
||||||
'assets/optimized/portrait/culture-480.avif 480w, assets/optimized/portrait/culture-720.avif 720w, assets/optimized/portrait/culture-960.avif 960w',
|
|
||||||
webpSrcset:
|
|
||||||
'assets/optimized/portrait/culture-480.webp 480w, assets/optimized/portrait/culture-720.webp 720w, assets/optimized/portrait/culture-960.webp 960w',
|
|
||||||
width: 960,
|
|
||||||
height: 723,
|
|
||||||
alt: 'Julian visiting cultural landmarks',
|
|
||||||
text: 'Culture',
|
text: 'Culture',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -163,9 +114,7 @@ export const global: Global = {
|
|||||||
description:
|
description:
|
||||||
'A support system integrating various platforms and media to offer employees a unified overview.',
|
'A support system integrating various platforms and media to offer employees a unified overview.',
|
||||||
languages: ['TypeScript'],
|
languages: ['TypeScript'],
|
||||||
icon: 'assets/logos/synhost-dark.webp',
|
icon: 'https://gerlach-systems.de/IMAGES/SYNHOST/SYNHOST_DARK.png',
|
||||||
iconWidth: 230,
|
|
||||||
iconHeight: 136,
|
|
||||||
CircleIcon: false,
|
CircleIcon: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -174,9 +123,7 @@ export const global: Global = {
|
|||||||
description:
|
description:
|
||||||
'A support system integrating various platforms and media to offer employees a unified overview.',
|
'A support system integrating various platforms and media to offer employees a unified overview.',
|
||||||
languages: ['TypeScript'],
|
languages: ['TypeScript'],
|
||||||
icon: 'assets/logos/synhost-dark.webp',
|
icon: 'https://gerlach-systems.de/IMAGES/SYNHOST/SYNHOST_DARK.png',
|
||||||
iconWidth: 230,
|
|
||||||
iconHeight: 136,
|
|
||||||
CircleIcon: false,
|
CircleIcon: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -185,9 +132,7 @@ export const global: Global = {
|
|||||||
description:
|
description:
|
||||||
'A support system integrating various platforms and media to offer employees a unified overview.',
|
'A support system integrating various platforms and media to offer employees a unified overview.',
|
||||||
languages: ['TypeScript'],
|
languages: ['TypeScript'],
|
||||||
icon: 'assets/logos/synhost-dark.webp',
|
icon: 'https://gerlach-systems.de/IMAGES/SYNHOST/SYNHOST_DARK.png',
|
||||||
iconWidth: 230,
|
|
||||||
iconHeight: 136,
|
|
||||||
CircleIcon: false,
|
CircleIcon: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -5,19 +5,7 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<base href="/" />
|
<base href="/" />
|
||||||
|
|
||||||
<link rel="icon" type="image/webp" href="assets/optimized/portrait/me-480.webp" />
|
<link rel="icon" type="image/png" href="assets/portrait/me.png" />
|
||||||
<link
|
|
||||||
rel="preload"
|
|
||||||
as="image"
|
|
||||||
href="assets/optimized/portrait/me-960.jpg"
|
|
||||||
imagesrcset="
|
|
||||||
assets/optimized/portrait/me-480.avif 480w,
|
|
||||||
assets/optimized/portrait/me-720.avif 720w,
|
|
||||||
assets/optimized/portrait/me-960.avif 900w
|
|
||||||
"
|
|
||||||
imagesizes="(max-width: 640px) calc(100vw - 2rem), (max-width: 900px) min(560px, calc(100vw - 2rem)), 560px"
|
|
||||||
fetchpriority="high"
|
|
||||||
/>
|
|
||||||
<title>Lechner Julian</title>
|
<title>Lechner Julian</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -48,12 +48,7 @@ body {
|
|||||||
radial-gradient(circle at top left, rgba(217, 162, 140, 0.22), transparent 26%),
|
radial-gradient(circle at top left, rgba(217, 162, 140, 0.22), transparent 26%),
|
||||||
radial-gradient(circle at bottom right, rgba(127, 78, 63, 0.28), transparent 28%),
|
radial-gradient(circle at bottom right, rgba(127, 78, 63, 0.28), transparent 28%),
|
||||||
linear-gradient(180deg, rgba(9, 9, 12, 0.4), rgba(8, 8, 11, 0.82)),
|
linear-gradient(180deg, rgba(9, 9, 12, 0.4), rgba(8, 8, 11, 0.82)),
|
||||||
image-set(
|
url('assets/background.jpg') center / cover fixed no-repeat;
|
||||||
url('assets/optimized/background-1920.avif') type('image/avif'),
|
|
||||||
url('assets/optimized/background-1920.webp') type('image/webp'),
|
|
||||||
url('assets/optimized/background-1920.jpg') type('image/jpeg')
|
|
||||||
)
|
|
||||||
center / cover no-repeat;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
@@ -169,8 +164,7 @@ hr {
|
|||||||
#004dff 83.33%,
|
#004dff 83.33%,
|
||||||
#750787 83.33%,
|
#750787 83.33%,
|
||||||
#750787 100%
|
#750787 100%
|
||||||
)
|
) no-repeat 0 50%;
|
||||||
no-repeat 0 50%;
|
|
||||||
-webkit-background-clip: text;
|
-webkit-background-clip: text;
|
||||||
background-clip: text;
|
background-clip: text;
|
||||||
-webkit-text-fill-color: transparent;
|
-webkit-text-fill-color: transparent;
|
||||||
@@ -315,8 +309,7 @@ hr {
|
|||||||
#004dff 83.33%,
|
#004dff 83.33%,
|
||||||
#750787 83.33%,
|
#750787 83.33%,
|
||||||
#750787 100%
|
#750787 100%
|
||||||
)
|
) 0 50%;
|
||||||
0 50%;
|
|
||||||
transform: translate(2px, 2px);
|
transform: translate(2px, 2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -934,19 +927,6 @@ hr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
body {
|
|
||||||
background:
|
|
||||||
radial-gradient(circle at top left, rgba(217, 162, 140, 0.18), transparent 28%),
|
|
||||||
radial-gradient(circle at bottom right, rgba(127, 78, 63, 0.22), transparent 30%),
|
|
||||||
linear-gradient(180deg, rgba(9, 9, 12, 0.42), rgba(8, 8, 11, 0.84)),
|
|
||||||
image-set(
|
|
||||||
url('assets/optimized/background-1280.avif') type('image/avif'),
|
|
||||||
url('assets/optimized/background-1280.webp') type('image/webp'),
|
|
||||||
url('assets/optimized/background-1920.jpg') type('image/jpeg')
|
|
||||||
)
|
|
||||||
center / cover no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-section,
|
.hero-section,
|
||||||
.story-grid,
|
.story-grid,
|
||||||
.legal-grid,
|
.legal-grid,
|
||||||
|
|||||||