Using tailwind instead of scss as much as possible
This commit is contained in:
@@ -77,7 +77,7 @@ ng build
|
|||||||
|
|
||||||
* **Language:** TypeScript
|
* **Language:** TypeScript
|
||||||
* **Framework:** Angular
|
* **Framework:** Angular
|
||||||
* **Styling:** Only use tailwind, as much as possible. If not possible use SCSS (based on `styles.scss` and component-specific `.scss` files).
|
* **Styling:** Tailwind CSS (v3) is the primary styling approach. Use utility classes in templates. SCSS (`styles.scss`) is only for Angular Material theme setup, Material component overrides with `!important`, Swiper `::part()` selectors, and component `:host` blocks. Shared Tailwind component classes live in `src/tailwind.css` via `@layer components`.
|
||||||
* **Linting:** ESLint is configured (see `eslint.config.js` and `package.json` scripts).
|
* **Linting:** ESLint is configured (see `eslint.config.js` and `package.json` scripts).
|
||||||
* **Internationalization:** Uses `ngx-translate` with `en.json` and `de.json` asset files.
|
* **Internationalization:** Uses `ngx-translate` with `en.json` and `de.json` asset files.
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"styles": [
|
"styles": [
|
||||||
|
"src/tailwind.css",
|
||||||
"src/styles.scss",
|
"src/styles.scss",
|
||||||
"node_modules/swiper/swiper-bundle.css"
|
"node_modules/swiper/swiper-bundle.css"
|
||||||
]
|
]
|
||||||
@@ -95,6 +96,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"styles": [
|
"styles": [
|
||||||
|
"src/tailwind.css",
|
||||||
"src/styles.scss"
|
"src/styles.scss"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
794
package-lock.json
generated
794
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -39,6 +39,7 @@
|
|||||||
"angular-eslint": "21.3.1",
|
"angular-eslint": "21.3.1",
|
||||||
"eslint": "^10.0.3",
|
"eslint": "^10.0.3",
|
||||||
"jasmine-core": "~6.1.0",
|
"jasmine-core": "~6.1.0",
|
||||||
|
"tailwindcss": "^3.4.19",
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~5.9.3",
|
||||||
"typescript-eslint": "8.58.2"
|
"typescript-eslint": "8.58.2"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<app-particles-background></app-particles-background>
|
<app-particles-background></app-particles-background>
|
||||||
<app-topbar />
|
<app-topbar />
|
||||||
<main class="app-container app-surface">
|
<main class="w-full max-w-app mx-auto mt-4 grow text-app-fg transition-colors duration-[220ms]">
|
||||||
<router-outlet />
|
<router-outlet />
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer class="foot">
|
<footer class="border-t border-black/[.08] p-fluid-md text-center opacity-80 bg-app-bg">
|
||||||
<small>© {{ currentYear }} Andreas Dahm - {{ `APP.COPYRIGHT` | translate }}</small>
|
<small>© {{ currentYear }} Andreas Dahm - {{ `APP.COPYRIGHT` | translate }}</small>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
@@ -1,23 +1,27 @@
|
|||||||
<mat-toolbar class="topbar" color="primary" (keydown)="onKeydown($event)">
|
<mat-toolbar class="!flex !items-center !p-[clamp(0.5rem,1vw,1rem)] !backdrop-blur-[8px] !backdrop-saturate-[1.1] !bg-white/80 dark:!bg-[#313131]/80 !border-b !border-black/[.08]" color="primary" (keydown)="onKeydown($event)">
|
||||||
<a class="brand" routerLink="/">
|
<a class="flex items-center gap-[clamp(0.4rem,1vw,0.6rem)] text-inherit no-underline" routerLink="/">
|
||||||
<img class="logo-dot" src="{{AssetsConstants.LOGO}}" alt="" aria-hidden="true" draggable="false"
|
<img class="w-[clamp(36px,10vw,48px)] h-[clamp(36px,10vw,48px)] rounded-full" src="{{AssetsConstants.LOGO}}" alt="" aria-hidden="true" draggable="false"
|
||||||
oncontextmenu="return false;">
|
oncontextmenu="return false;">
|
||||||
<span class="brand-text">{{ 'APP.TITLE' | translate }}</span>
|
<span class="font-semibold tracking-[0.2px] text-[clamp(1rem,3vw,1.2rem)]">{{ 'APP.TITLE' | translate }}</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<nav class="nav">
|
<nav class="absolute left-1/2 -translate-x-1/2 flex gap-[clamp(0.25rem,1vw,0.5rem)] justify-center mobile:hidden">
|
||||||
<a [routerLink]="RouterConstants.ABOUT.LINK" routerLinkActive="active" mat-button>{{ 'TOPBAR.ABOUT' | translate }}</a>
|
<a class="opacity-70 transition-opacity duration-150 hover:opacity-100 relative after:content-[''] after:absolute after:bottom-1 after:left-2.5 after:right-2.5 after:h-0.5 after:bg-current after:rounded-sm after:scale-x-0 after:transition-transform [&.active]:opacity-100 [&.active]:after:scale-x-100"
|
||||||
<a [routerLink]="RouterConstants.PROJECTS.LINK" routerLinkActive="active" mat-button>{{ 'TOPBAR.PROJECTS' | translate }}</a>
|
[routerLink]="RouterConstants.ABOUT.LINK" routerLinkActive="active" mat-button>{{ 'TOPBAR.ABOUT' | translate }}</a>
|
||||||
<a [routerLink]="RouterConstants.ALGORITHMS.LINK" routerLinkActive="active" mat-button>{{ 'TOPBAR.ALGORITHMS' | translate }}</a>
|
<a class="opacity-70 transition-opacity duration-150 hover:opacity-100 relative after:content-[''] after:absolute after:bottom-1 after:left-2.5 after:right-2.5 after:h-0.5 after:bg-current after:rounded-sm after:scale-x-0 after:transition-transform [&.active]:opacity-100 [&.active]:after:scale-x-100"
|
||||||
<a [routerLink]="RouterConstants.IMPRINT.LINK" routerLinkActive="active" mat-button>{{ 'TOPBAR.IMPRINT' | translate }}</a>
|
[routerLink]="RouterConstants.PROJECTS.LINK" routerLinkActive="active" mat-button>{{ 'TOPBAR.PROJECTS' | translate }}</a>
|
||||||
|
<a class="opacity-70 transition-opacity duration-150 hover:opacity-100 relative after:content-[''] after:absolute after:bottom-1 after:left-2.5 after:right-2.5 after:h-0.5 after:bg-current after:rounded-sm after:scale-x-0 after:transition-transform [&.active]:opacity-100 [&.active]:after:scale-x-100"
|
||||||
|
[routerLink]="RouterConstants.ALGORITHMS.LINK" routerLinkActive="active" mat-button>{{ 'TOPBAR.ALGORITHMS' | translate }}</a>
|
||||||
|
<a class="opacity-70 transition-opacity duration-150 hover:opacity-100 relative after:content-[''] after:absolute after:bottom-1 after:left-2.5 after:right-2.5 after:h-0.5 after:bg-current after:rounded-sm after:scale-x-0 after:transition-transform [&.active]:opacity-100 [&.active]:after:scale-x-100"
|
||||||
|
[routerLink]="RouterConstants.IMPRINT.LINK" routerLinkActive="active" mat-button>{{ 'TOPBAR.IMPRINT' | translate }}</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<!-- Mobile nav menu button -->
|
<!-- Mobile nav menu button -->
|
||||||
<button mat-icon-button class="nav-menu-btn" [matMenuTriggerFor]="navMenu" aria-label="Open navigation">
|
<button mat-icon-button class="hidden mobile:inline-flex" [matMenuTriggerFor]="navMenu" aria-label="Open navigation">
|
||||||
<mat-icon>menu</mat-icon>
|
<mat-icon>menu</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<span class="spacer"></span>
|
<span class="flex-1"></span>
|
||||||
|
|
||||||
<!-- Mobile nav menu -->
|
<!-- Mobile nav menu -->
|
||||||
<mat-menu #navMenu="matMenu" xPosition="before">
|
<mat-menu #navMenu="matMenu" xPosition="before">
|
||||||
@@ -35,7 +39,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
|
|
||||||
<span class="spacer"></span>
|
<span class="flex-1"></span>
|
||||||
|
|
||||||
<!-- Settings: Sprache + Theme -->
|
<!-- Settings: Sprache + Theme -->
|
||||||
<button mat-icon-button [matMenuTriggerFor]="settingsMenu" aria-label="Open settings"
|
<button mat-icon-button [matMenuTriggerFor]="settingsMenu" aria-label="Open settings"
|
||||||
|
|||||||
@@ -1,107 +1,6 @@
|
|||||||
/* ---- Topbar Host & Base ---- */
|
|
||||||
:host {
|
:host {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.topbar {
|
|
||||||
/* Erzeugt den Milchglas-Effekt */
|
|
||||||
backdrop-filter: saturate(1.1) blur(8px);
|
|
||||||
-webkit-backdrop-filter: saturate(1.1) blur(8px);
|
|
||||||
/* Safari Support */
|
|
||||||
|
|
||||||
/* Mischt die Variable mit Transparenz. !important überschreibt Material-Vorgaben */
|
|
||||||
background: color-mix(in oklab, var(--app-topbar-bg) 80%, transparent) !important;
|
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, .08);
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: clamp(0.5rem, 1vw, 1rem);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- Branding ---- */
|
|
||||||
.brand {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: clamp(0.4rem, 1vw, 0.6rem);
|
|
||||||
color: inherit;
|
|
||||||
text-decoration: none;
|
|
||||||
|
|
||||||
.logo-dot {
|
|
||||||
width: clamp(36px, 10vw, 48px);
|
|
||||||
height: clamp(36px, 10vw, 48px);
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.brand-text {
|
|
||||||
font-weight: 600;
|
|
||||||
letter-spacing: .2px;
|
|
||||||
font-size: clamp(1rem, 3vw, 1.2rem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- Navigation ---- */
|
|
||||||
.nav {
|
|
||||||
position: absolute;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
display: flex;
|
|
||||||
gap: clamp(0.25rem, 1vw, 0.5rem);
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav a {
|
|
||||||
opacity: 0.72;
|
|
||||||
transition: opacity 150ms ease;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
bottom: 4px;
|
|
||||||
left: 10px;
|
|
||||||
right: 10px;
|
|
||||||
height: 2px;
|
|
||||||
background: currentColor;
|
|
||||||
border-radius: 2px;
|
|
||||||
transform: scaleX(0);
|
|
||||||
transition: transform 200ms ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
opacity: 1;
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
transform: scaleX(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-menu-btn {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spacer {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- Mobile Responsiveness ---- */
|
|
||||||
@media (max-width: 760px) {
|
|
||||||
.nav {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-menu-btn {
|
|
||||||
display: inline-flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.brand {
|
|
||||||
flex: unset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +1,34 @@
|
|||||||
<section class="about">
|
<section class="grid gap-fluid-md max-w-app mx-4 mt-auto">
|
||||||
<mat-card class="hero">
|
<mat-card>
|
||||||
<div class="hero-flex-container">
|
<div class="flex flex-wrap gap-fluid-lg p-fluid-md items-start">
|
||||||
<div class="photo">
|
<div class="flex-[1_1_min(100%,425px)] max-w-full flex justify-center">
|
||||||
<img [ngSrc]="AssetsConstants.ME" width="421" height="512" alt="{{ 'ABOUT.ALT.PROFILE' | translate }}"
|
<img class="block w-full h-auto max-w-[425px] rounded-xl shadow-[0_6px_24px_rgba(0,0,0,0.25)] object-cover"
|
||||||
|
[ngSrc]="AssetsConstants.ME" width="421" height="512" alt="{{ 'ABOUT.ALT.PROFILE' | translate }}"
|
||||||
draggable="false" oncontextmenu="return false;" priority />
|
draggable="false" oncontextmenu="return false;" priority />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="intro">
|
<div class="flex-[999_1_min(100%,400px)]">
|
||||||
<h1>{{ 'ABOUT.HELLO' | translate }}</h1>
|
<h1 class="mt-0 mb-2 text-[clamp(1.5rem,5vw,2.5rem)]">{{ 'ABOUT.HELLO' | translate }}</h1>
|
||||||
<p class="lead">
|
<p class="opacity-90 my-2 mb-4 text-[clamp(1rem,2.5vw,1.15rem)]">
|
||||||
{{ 'ABOUT.LEAD' | translate }}
|
{{ 'ABOUT.LEAD' | translate }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="meta">
|
<div class="flex flex-col gap-1 mb-2">
|
||||||
<div class="row">
|
<div class="flex items-center flex-wrap gap-[0.4rem]">
|
||||||
<mat-icon aria-hidden="true">work</mat-icon>
|
<mat-icon aria-hidden="true">work</mat-icon>
|
||||||
<span>{{ 'ABOUT.ROLE' | translate }}</span>
|
<span>{{ 'ABOUT.ROLE' | translate }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="flex items-center flex-wrap gap-[0.4rem]">
|
||||||
<mat-icon aria-hidden="true">location_on</mat-icon>
|
<mat-icon aria-hidden="true">location_on</mat-icon>
|
||||||
<span>{{ 'ABOUT.LOCATION' | translate }}</span>
|
<span>{{ 'ABOUT.LOCATION' | translate }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="flex items-center flex-wrap gap-[0.4rem]">
|
||||||
<mat-icon aria-hidden="true">email</mat-icon>
|
<mat-icon aria-hidden="true">email</mat-icon>
|
||||||
<a href="" (click)="SharedFunctions.openMail($event)">
|
<a href="" (click)="SharedFunctions.openMail($event)">
|
||||||
{{ 'ABOUT.CONTACT_ME' | translate }}
|
{{ 'ABOUT.CONTACT_ME' | translate }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="flex items-center flex-wrap gap-[0.4rem]">
|
||||||
<mat-icon>data_object</mat-icon>
|
<mat-icon>data_object</mat-icon>
|
||||||
<a href="{{UrlConstants.CODEBERG}}" target="_blank" rel="noopener">Codeberg</a>
|
<a href="{{UrlConstants.CODEBERG}}" target="_blank" rel="noopener">Codeberg</a>
|
||||||
<span>·</span>
|
<span>·</span>
|
||||||
@@ -39,11 +40,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
|
|
||||||
<mat-card class="skills">
|
<mat-card class="p-[clamp(5px,2vw,15px)]">
|
||||||
<h2>{{ 'ABOUT.SECTION.SKILLS' | translate }}</h2>
|
<h2 class="mt-1 ml-1 text-[clamp(1.2rem,4vw,1.8rem)]">{{ 'ABOUT.SECTION.SKILLS' | translate }}</h2>
|
||||||
<div class="chip-groups">
|
<div class="grid grid-cols-[repeat(auto-fit,minmax(min(100%,250px),1fr))] gap-fluid-sm ml-1 mb-2">
|
||||||
<div>
|
<div>
|
||||||
<h3>{{ 'ABOUT.SECTION.BACKEND_ARCH' | translate }}</h3>
|
<h3 class="my-1 text-[0.95rem] opacity-85">{{ 'ABOUT.SECTION.BACKEND_ARCH' | translate }}</h3>
|
||||||
<mat-chip-set aria-label="Backend and Architecture">
|
<mat-chip-set aria-label="Backend and Architecture">
|
||||||
@for (s of skillsArchitecture; track s) {
|
@for (s of skillsArchitecture; track s) {
|
||||||
<mat-chip>{{ s | translate }}</mat-chip>
|
<mat-chip>{{ s | translate }}</mat-chip>
|
||||||
@@ -52,7 +53,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3>{{ 'ABOUT.SECTION.INFRA_CLOUD' | translate }}</h3>
|
<h3 class="my-1 text-[0.95rem] opacity-85">{{ 'ABOUT.SECTION.INFRA_CLOUD' | translate }}</h3>
|
||||||
<mat-chip-set aria-label="Infrastructure and Cloud">
|
<mat-chip-set aria-label="Infrastructure and Cloud">
|
||||||
@for (s of skillsCore; track s) {
|
@for (s of skillsCore; track s) {
|
||||||
<mat-chip>{{ s | translate }}</mat-chip>
|
<mat-chip>{{ s | translate }}</mat-chip>
|
||||||
@@ -61,7 +62,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3>{{ 'ABOUT.SECTION.SIM_ALGO' | translate }}</h3>
|
<h3 class="my-1 text-[0.95rem] opacity-85">{{ 'ABOUT.SECTION.SIM_ALGO' | translate }}</h3>
|
||||||
<mat-chip-set aria-label="Simulation and Algorithms">
|
<mat-chip-set aria-label="Simulation and Algorithms">
|
||||||
@for (s of skillsEngineering; track s) {
|
@for (s of skillsEngineering; track s) {
|
||||||
<mat-chip>{{ s | translate }}</mat-chip>
|
<mat-chip>{{ s | translate }}</mat-chip>
|
||||||
@@ -71,31 +72,31 @@
|
|||||||
</div>
|
</div>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
|
|
||||||
<mat-card class="experdience">
|
<mat-card class="p-[clamp(5px,2vw,15px)]">
|
||||||
<h2 style="margin-left: 0.5rem;">{{ 'ABOUT.SECTION.EXPERIENCE' | translate }}</h2>
|
<h2 class="mt-1 ml-2 text-[clamp(1.2rem,4vw,1.8rem)]">{{ 'ABOUT.SECTION.EXPERIENCE' | translate }}</h2>
|
||||||
<div class="xp-list">
|
<div class="ml-1 grid gap-fluid-sm">
|
||||||
@for (entry of xpKeys; track entry.key) {
|
@for (entry of xpKeys; track entry.key) {
|
||||||
<div class="xp-item">
|
<div>
|
||||||
<div class="xp-head-grid">
|
<div class="grid grid-cols-[auto_1fr] gap-x-3">
|
||||||
<div class="logo-wrap">
|
<div class="row-span-2 col-start-1 flex items-center">
|
||||||
<img src="{{entry.logo}}" alt="" class="company-logo" aria-hidden="true" />
|
<img src="{{entry.logo}}" alt="" class="w-10 h-10 object-contain opacity-90 rounded-[10%] bg-logo-bg" aria-hidden="true" />
|
||||||
</div>
|
</div>
|
||||||
<div class="head-row">
|
<div class="row-start-1 col-start-2 flex flex-wrap items-baseline gap-x-4 gap-y-1">
|
||||||
<strong>{{ (entry.key + '.ROLE') | translate }}</strong>
|
<strong class="text-[clamp(0.95rem,2.5vw,1.1rem)]">{{ (entry.key + '.ROLE') | translate }}</strong>
|
||||||
<span class="time">{{ (entry.key + '.TIME') | translate }}</span>
|
<span class="opacity-75 text-[clamp(0.85rem,2vw,0.95rem)]">{{ (entry.key + '.TIME') | translate }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="company-row">
|
<div class="row-start-2 col-start-2 mt-[0.1rem] opacity-85 text-[clamp(0.85rem,2vw,1rem)]">
|
||||||
{{ (entry.key + '.COMPANY') | translate }}
|
{{ (entry.key + '.COMPANY') | translate }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@if (!entry.no_highlights) {
|
@if (!entry.no_highlights) {
|
||||||
<div class="highlights">
|
<div class="mt-[0.4rem] ml-[clamp(0.25rem,1vw,0.75rem)] pl-[clamp(0.8rem,2vw,1.2rem)]">
|
||||||
<ul>
|
<ul>
|
||||||
<li>{{ entry.key + '.HIGHLIGHTS.P1' | translate }}</li>
|
<li class="my-1 text-[clamp(0.9rem,2vw,1rem)]">{{ entry.key + '.HIGHLIGHTS.P1' | translate }}</li>
|
||||||
<li>{{ entry.key + '.HIGHLIGHTS.P2' | translate }}</li>
|
<li class="my-1 text-[clamp(0.9rem,2vw,1rem)]">{{ entry.key + '.HIGHLIGHTS.P2' | translate }}</li>
|
||||||
<li>{{ entry.key + '.HIGHLIGHTS.P3' | translate }}</li>
|
<li class="my-1 text-[clamp(0.9rem,2vw,1rem)]">{{ entry.key + '.HIGHLIGHTS.P3' | translate }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -108,38 +109,38 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
<mat-card class="projects">
|
<mat-card class="p-[clamp(5px,2vw,15px)]">
|
||||||
<h2>{{ 'ABOUT.SECTION.PROJECTS' | translate }}</h2>
|
<h2 class="mt-1 ml-1 text-[clamp(1.2rem,4vw,1.8rem)]">{{ 'ABOUT.SECTION.PROJECTS' | translate }}</h2>
|
||||||
|
|
||||||
<div class="xp-list">
|
<div class="ml-1 grid gap-fluid-sm">
|
||||||
@for (entry of projectKeys; track entry.key) {
|
@for (entry of projectKeys; track entry.key) {
|
||||||
<div class="xp-item">
|
<div>
|
||||||
<div class="head-row">
|
<div class="flex flex-wrap items-baseline gap-x-4 gap-y-1">
|
||||||
<strong>{{ (entry.key + '.TITLE') | translate }}</strong>
|
<strong class="text-[clamp(0.95rem,2.5vw,1.1rem)]">{{ (entry.key + '.TITLE') | translate }}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="company-row">
|
<div class="mt-[0.1rem] opacity-85 text-[clamp(0.85rem,2vw,1rem)]">
|
||||||
{{ (entry.key + '.DESCRIPTION') | translate }}
|
{{ (entry.key + '.DESCRIPTION') | translate }}
|
||||||
</div>
|
</div>
|
||||||
@if (entry.externalLink) {
|
@if (entry.externalLink) {
|
||||||
<div class="link-row">
|
<div class="mt-[0.1rem] opacity-85">
|
||||||
<a class="link-with-icon" href="{{entry.externalLink}}" target="_blank" rel="noopener noreferrer">
|
<a class="inline-flex items-center gap-[0.35rem] leading-none" href="{{entry.externalLink}}" target="_blank" rel="noopener noreferrer">
|
||||||
<mat-icon>open_in_new</mat-icon>
|
<mat-icon class="!text-[18px] !w-[18px] !h-[18px]">open_in_new</mat-icon>
|
||||||
{{ (entry.key + '.LINK_EXTERNAL') | translate }}
|
{{ (entry.key + '.LINK_EXTERNAL') | translate }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<div class="link-row">
|
<div class="mt-[0.1rem] opacity-85">
|
||||||
<a class="link-with-icon" [routerLink]="['/projects']" [queryParams]="{ project: entry.identifier }"
|
<a class="inline-flex items-center gap-[0.35rem] leading-none" [routerLink]="['/projects']" [queryParams]="{ project: entry.identifier }"
|
||||||
rel="noopener noreferrer">
|
rel="noopener noreferrer">
|
||||||
<mat-icon>link</mat-icon>
|
<mat-icon class="!text-[18px] !w-[18px] !h-[18px]">link</mat-icon>
|
||||||
{{ (entry.key + '.LINK_INTERNAL') | translate }}
|
{{ (entry.key + '.LINK_INTERNAL') | translate }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="highlights-noMargin">
|
<div>
|
||||||
<ul>
|
<ul>
|
||||||
<li>{{ entry.key + '.HIGHLIGHTS.P1' | translate }}</li>
|
<li class="my-1 text-[clamp(0.9rem,2vw,1rem)]">{{ entry.key + '.HIGHLIGHTS.P1' | translate }}</li>
|
||||||
<li>{{ entry.key + '.HIGHLIGHTS.P2' | translate }}</li>
|
<li class="my-1 text-[clamp(0.9rem,2vw,1rem)]">{{ entry.key + '.HIGHLIGHTS.P2' | translate }}</li>
|
||||||
<li>{{ entry.key + '.HIGHLIGHTS.P3' | translate }}</li>
|
<li class="my-1 text-[clamp(0.9rem,2vw,1rem)]">{{ entry.key + '.HIGHLIGHTS.P3' | translate }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -152,23 +153,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
|
|
||||||
<mat-card class="education">
|
<mat-card class="p-[clamp(5px,2vw,15px)]">
|
||||||
<h2>{{ 'ABOUT.SECTION.EDUCATION' | translate }}</h2>
|
<h2 class="mt-1 ml-1 text-[clamp(1.2rem,4vw,1.8rem)]">{{ 'ABOUT.SECTION.EDUCATION' | translate }}</h2>
|
||||||
|
|
||||||
<div class="xp-list">
|
<div class="ml-1 grid gap-fluid-sm">
|
||||||
<div class="xp-item">
|
<div>
|
||||||
@for (entry of educationKeys; track entry.key) {
|
@for (entry of educationKeys; track entry.key) {
|
||||||
<div class="head-row">
|
<div class="flex flex-wrap items-baseline gap-x-4 gap-y-1">
|
||||||
<strong>{{ (entry.key + '.WHERE') | translate }}</strong>
|
<strong class="text-[clamp(0.95rem,2.5vw,1.1rem)]">{{ (entry.key + '.WHERE') | translate }}</strong>
|
||||||
<span class="time">{{ (entry.key + '.WHEN') | translate }}</span>
|
<span class="opacity-75 text-[clamp(0.85rem,2vw,0.95rem)]">{{ (entry.key + '.WHEN') | translate }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="company-row">
|
<div class="mt-[0.1rem] opacity-85 text-[clamp(0.85rem,2vw,1rem)]">
|
||||||
{{ (entry.key + '.WHAT') | translate }}
|
{{ (entry.key + '.WHAT') | translate }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if(entry.key !== educationKeys.at(educationKeys.length-1)?.key)
|
@if(entry.key !== educationKeys.at(educationKeys.length-1)?.key)
|
||||||
{
|
{
|
||||||
<mat-divider style="margin-top: .5rem; margin-bottom: .5rem"></mat-divider>
|
<mat-divider class="!my-2"></mat-divider>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
<div class="card-grid">
|
<div class="grid gap-fluid-md grid-cols-[repeat(auto-fill,minmax(min(100%,450px),1fr))] max-w-app mx-4 mt-auto">
|
||||||
<h1 class="algo-page-title">{{ 'ALGORITHM.TITLE' | translate }}</h1>
|
<h1 class="m-0 mb-2 text-[clamp(1.4rem,4vw,2rem)]">{{ 'ALGORITHM.TITLE' | translate }}</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-grid">
|
<div class="grid gap-fluid-md grid-cols-[repeat(auto-fill,minmax(min(100%,450px),1fr))] max-w-app mx-4 mt-auto">
|
||||||
@for (category of categories; track category.id) {
|
@for (category of categories; track category.id) {
|
||||||
<mat-card class="algo-card" [routerLink]="[category.routerLink]">
|
<mat-card class="card-gradient-bar transition-transform duration-200 ease-in-out flex flex-col cursor-pointer hover:-translate-y-1 hover:shadow-[0_8px_24px_rgba(0,0,0,0.12)]" [routerLink]="[category.routerLink]">
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<div class="algo-icon-wrap">
|
<div class="flex items-center text-[var(--mat-sys-primary)] mb-4">
|
||||||
<mat-icon>{{ category.icon }}</mat-icon>
|
<mat-icon class="!text-[26px] !w-[26px] !h-[26px]">{{ category.icon }}</mat-icon>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="algo-card-title">{{ category.title | translate }}</h3>
|
<h3 class="text-[1.05rem] font-semibold mb-2 m-0">{{ category.title | translate }}</h3>
|
||||||
<p class="algo-card-desc">{{ category.description | translate }}</p>
|
<p class="m-0 opacity-75 text-sm leading-relaxed">{{ category.description | translate }}</p>
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<mat-card class="algo-container">
|
<mat-card class="w-full max-w-[1920px] p-5">
|
||||||
<mat-card-header>
|
<mat-card-header>
|
||||||
<mat-card-title>{{ 'CLOTH.TITLE' | translate }}</mat-card-title>
|
<mat-card-title>{{ 'CLOTH.TITLE' | translate }}</mat-card-title>
|
||||||
</mat-card-header>
|
</mat-card-header>
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<app-information [algorithmInformation]="algoInformation"/>
|
<app-information [algorithmInformation]="algoInformation"/>
|
||||||
<div class="controls-container">
|
<div class="flex flex-col mb-4">
|
||||||
<div class="controls-panel">
|
<div class="flex gap-4 mb-4 items-center flex-wrap mt-2.5 text-[0.9em]">
|
||||||
<button mat-raised-button color="primary" (click)="toggleWind()">
|
<button mat-raised-button color="primary" (click)="toggleWind()">
|
||||||
{{ isWindActive ? ('CLOTH.WIND_OFF' | translate) : ('CLOTH.WIND_ON' | translate) }}
|
{{ isWindActive ? ('CLOTH.WIND_OFF' | translate) : ('CLOTH.WIND_ON' | translate) }}
|
||||||
</button>
|
</button>
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
{{ 'CLOTH.RESTART_SIMULATION' | translate }}
|
{{ 'CLOTH.RESTART_SIMULATION' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="sliders-panel">
|
<div class="flex items-center gap-2.5">
|
||||||
<span>{{ 'CLOTH.ELONGATION' | translate }}: {{ elongation }}</span>
|
<span>{{ 'CLOTH.ELONGATION' | translate }}: {{ elongation }}</span>
|
||||||
<mat-slider min="0.5" max="2.0" step="0.1">
|
<mat-slider min="0.5" max="2.0" step="0.1">
|
||||||
<input matSliderThumb [(ngModel)]="elongation">
|
<input matSliderThumb [(ngModel)]="elongation">
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<mat-card class="algo-container">
|
<mat-card class="w-full max-w-[1920px] p-5">
|
||||||
<mat-card-header>
|
<mat-card-header>
|
||||||
<mat-card-title>{{ 'GOL.TITLE' | translate }}</mat-card-title>
|
<mat-card-title>{{ 'GOL.TITLE' | translate }}</mat-card-title>
|
||||||
</mat-card-header>
|
</mat-card-header>
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<app-information [algorithmInformation]="algoInformation"/>
|
<app-information [algorithmInformation]="algoInformation"/>
|
||||||
<div class="controls-container">
|
<div class="flex flex-col mb-4">
|
||||||
<div class="controls-panel">
|
<div class="flex gap-4 mb-4 items-center flex-wrap mt-2.5 text-[0.9em]">
|
||||||
<button mat-raised-button (click)="generate(Scenario.SIMPLE)">
|
<button mat-raised-button (click)="generate(Scenario.SIMPLE)">
|
||||||
<mat-icon>arrow_right</mat-icon> {{ 'GOL.SIMPLE_SCENE' | translate }}
|
<mat-icon>arrow_right</mat-icon> {{ 'GOL.SIMPLE_SCENE' | translate }}
|
||||||
</button>
|
</button>
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
<mat-icon>check_box_outline_blank</mat-icon> {{ 'GOL.EMPTY_SCENE' | translate }}
|
<mat-icon>check_box_outline_blank</mat-icon> {{ 'GOL.EMPTY_SCENE' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="controls-panel">
|
<div class="flex gap-4 mb-4 items-center flex-wrap mt-2.5 text-[0.9em]">
|
||||||
@if (gameStarted())
|
@if (gameStarted())
|
||||||
{
|
{
|
||||||
<button mat-raised-button (click)="pauseGame()">
|
<button mat-raised-button (click)="pauseGame()">
|
||||||
@@ -35,8 +35,8 @@
|
|||||||
}
|
}
|
||||||
<p>{{ 'SORTING.EXECUTION_TIME' | translate }}: {{ executionTime }} ms</p>
|
<p>{{ 'SORTING.EXECUTION_TIME' | translate }}: {{ executionTime }} ms</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-container">
|
<div class="flex gap-3 items-center flex-wrap">
|
||||||
<mat-form-field appearance="outline" class="input-field">
|
<mat-form-field appearance="outline" class="w-[150px]">
|
||||||
<mat-label>{{ 'ALGORITHM.GRID_HEIGHT' | translate }}</mat-label>
|
<mat-label>{{ 'ALGORITHM.GRID_HEIGHT' | translate }}</mat-label>
|
||||||
<input
|
<input
|
||||||
matInput
|
matInput
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
(ngModelChange)="pauseGame(); genericGridComponent.gridRows = gridRows; genericGridComponent.applyGridSize()"
|
(ngModelChange)="pauseGame(); genericGridComponent.gridRows = gridRows; genericGridComponent.applyGridSize()"
|
||||||
/>
|
/>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field appearance="outline" class="input-field">
|
<mat-form-field appearance="outline" class="w-[150px]">
|
||||||
<mat-label>{{ 'ALGORITHM.GRID_WIDTH' | translate }}</mat-label>
|
<mat-label>{{ 'ALGORITHM.GRID_WIDTH' | translate }}</mat-label>
|
||||||
<input
|
<input
|
||||||
matInput
|
matInput
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
(ngModelChange)="pauseGame(); genericGridComponent.gridCols = gridCols; genericGridComponent.applyGridSize()"
|
(ngModelChange)="pauseGame(); genericGridComponent.gridCols = gridCols; genericGridComponent.applyGridSize()"
|
||||||
/>
|
/>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field appearance="outline" class="input-field">
|
<mat-form-field appearance="outline" class="w-[150px]">
|
||||||
<mat-label>{{ 'GOL.SPEED' | translate }}</mat-label>
|
<mat-label>{{ 'GOL.SPEED' | translate }}</mat-label>
|
||||||
<input
|
<input
|
||||||
matInput
|
matInput
|
||||||
@@ -72,9 +72,9 @@
|
|||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="legend">
|
<div class="flex flex-wrap gap-4 items-center text-[0.9em]">
|
||||||
<span><span class="legend-color alive"></span> {{ 'GOL.ALIVE' | translate }}</span>
|
<span><span class="legend-swatch bg-black"></span> {{ 'GOL.ALIVE' | translate }}</span>
|
||||||
<span><span class="legend-color empty"></span> {{ 'GOL.DEAD' | translate }}</span>
|
<span><span class="legend-swatch bg-gray-300"></span> {{ 'GOL.DEAD' | translate }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<app-generic-grid
|
<app-generic-grid
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
<mat-card class="algo-container">
|
<mat-card class="w-full max-w-[1920px] p-5">
|
||||||
<mat-card-header>
|
<mat-card-header>
|
||||||
<mat-card-title>{{ 'FOUR_COLOR.TITLE' | translate }}</mat-card-title>
|
<mat-card-title>{{ 'FOUR_COLOR.TITLE' | translate }}</mat-card-title>
|
||||||
</mat-card-header>
|
</mat-card-header>
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<app-information [algorithmInformation]="algoInformation"/>
|
<app-information [algorithmInformation]="algoInformation"/>
|
||||||
|
|
||||||
<div class="controls-container">
|
<div class="flex flex-col mb-4">
|
||||||
<div class="controls-panel">
|
<div class="flex gap-4 mb-4 items-center flex-wrap mt-2.5 text-[0.9em]">
|
||||||
<button mat-flat-button color="primary" (click)="generateNewMap()">{{ 'FOUR_COLOR.GENERATE' | translate }}</button>
|
<button mat-flat-button color="primary" (click)="generateNewMap()">{{ 'FOUR_COLOR.GENERATE' | translate }}</button>
|
||||||
<button mat-flat-button color="accent" (click)="autoSolve()">{{ 'FOUR_COLOR.SOLVE' | translate }}</button>
|
<button mat-flat-button color="accent" (click)="autoSolve()">{{ 'FOUR_COLOR.SOLVE' | translate }}</button>
|
||||||
<button mat-stroked-button (click)="resetColors()">{{ 'FOUR_COLOR.CLEAR' | translate }}</button>
|
<button mat-stroked-button (click)="resetColors()">{{ 'FOUR_COLOR.CLEAR' | translate }}</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="controls-panel">
|
<div class="flex gap-4 mb-4 items-center flex-wrap mt-2.5 text-[0.9em]">
|
||||||
<div class="input-container">
|
<div class="flex gap-3 items-center flex-wrap">
|
||||||
<mat-form-field appearance="outline" class="input-field">
|
<mat-form-field appearance="outline" class="w-[150px]">
|
||||||
<mat-label>{{ 'ALGORITHM.GRID_HEIGHT' | translate }}</mat-label>
|
<mat-label>{{ 'ALGORITHM.GRID_HEIGHT' | translate }}</mat-label>
|
||||||
<input
|
<input
|
||||||
matInput
|
matInput
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
/>
|
/>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="input-field">
|
<mat-form-field appearance="outline" class="w-[150px]">
|
||||||
<mat-label>{{ 'ALGORITHM.GRID_WIDTH' | translate }}</mat-label>
|
<mat-label>{{ 'ALGORITHM.GRID_WIDTH' | translate }}</mat-label>
|
||||||
<input
|
<input
|
||||||
matInput
|
matInput
|
||||||
@@ -40,21 +40,28 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="legend">
|
<div class="flex flex-wrap gap-4 items-center text-[0.9em]">
|
||||||
<span><span class="legend-color color1"></span> {{ 'FOUR_COLOR.COLOR_1' | translate }}</span>
|
<span><span class="legend-swatch bg-[#FF5252]"></span> {{ 'FOUR_COLOR.COLOR_1' | translate }}</span>
|
||||||
<span><span class="legend-color color2"></span> {{ 'FOUR_COLOR.COLOR_2' | translate }}</span>
|
<span><span class="legend-swatch bg-[#448AFF]"></span> {{ 'FOUR_COLOR.COLOR_2' | translate }}</span>
|
||||||
<span><span class="legend-color color3"></span> {{ 'FOUR_COLOR.COLOR_3' | translate }}</span>
|
<span><span class="legend-swatch bg-[#4CAF50]"></span> {{ 'FOUR_COLOR.COLOR_3' | translate }}</span>
|
||||||
<span><span class="legend-color color4"></span> {{ 'FOUR_COLOR.COLOR_4' | translate }}</span>
|
<span><span class="legend-swatch bg-[#FFEB3B]"></span> {{ 'FOUR_COLOR.COLOR_4' | translate }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="status-panel" [ngClass]="solutionStatus.toLowerCase()">
|
<div class="mt-5 flex gap-2.5 py-2.5 px-4 rounded border-l-[5px] font-medium min-w-[300px] items-center"
|
||||||
<span class="status-label">{{ 'FOUR_COLOR.STATUS.LABEL' | translate }}:</span>
|
[ngClass]="{
|
||||||
<span class="status-message">{{ 'FOUR_COLOR.STATUS.' + solutionStatus | translate }}</span>
|
'border-l-gray-500 bg-app-bg': solutionStatus === 'INCOMPLETE',
|
||||||
|
'border-l-green-500 bg-green-50 text-green-800': solutionStatus === 'SOLVED',
|
||||||
|
'border-l-orange-500 bg-orange-50 text-orange-700': solutionStatus === 'CONFLICTS',
|
||||||
|
'border-l-red-500 bg-red-50 text-red-800': solutionStatus === 'INVALID'
|
||||||
|
}">
|
||||||
|
<span class="uppercase text-[0.8em] opacity-70">{{ 'FOUR_COLOR.STATUS.LABEL' | translate }}:</span>
|
||||||
|
<span>{{ 'FOUR_COLOR.STATUS.' + solutionStatus | translate }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="canvas-container">
|
<div class="flex justify-center items-center w-full max-w-[1000px] mx-auto">
|
||||||
<canvas #fourColorCanvas
|
<canvas #fourColorCanvas
|
||||||
|
class="cursor-pointer max-w-full h-auto [image-rendering:pixelated]"
|
||||||
(mousedown)="onMouseDown($event)"
|
(mousedown)="onMouseDown($event)"
|
||||||
(mousemove)="onMouseMove($event)"
|
(mousemove)="onMouseMove($event)"
|
||||||
(touchstart)="onTouchStart($event)"
|
(touchstart)="onTouchStart($event)"
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
|
|
||||||
.status-panel {
|
|
||||||
margin-top: 20px;
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
padding: 10px 15px;
|
|
||||||
border-radius: 4px;
|
|
||||||
background-color: var(--app-fg);
|
|
||||||
border-left: 5px solid #9e9e9e;
|
|
||||||
font-weight: 500;
|
|
||||||
min-width: 300px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-panel.incomplete {
|
|
||||||
border-left-color: #9e9e9e;
|
|
||||||
background-color: var(--app-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-panel.solved {
|
|
||||||
border-left-color: #4CAF50;
|
|
||||||
background-color: #e8f5e9;
|
|
||||||
color: #2e7d32;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-panel.conflicts {
|
|
||||||
border-left-color: #ff9800;
|
|
||||||
background-color: #fff3e0;
|
|
||||||
color: #ef6c00;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-panel.invalid {
|
|
||||||
border-left-color: #f44336;
|
|
||||||
background-color: #ffebee;
|
|
||||||
color: #c62828;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-label {
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-size: 0.8em;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.color1 { background-color: #FF5252; }
|
|
||||||
.color2 { background-color: #448AFF; }
|
|
||||||
.color3 { background-color: #4CAF50; }
|
|
||||||
.color4 { background-color: #FFEB3B; }
|
|
||||||
|
|
||||||
canvas {
|
|
||||||
cursor: pointer;
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
image-rendering: pixelated;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<mat-card class="algo-container">
|
<mat-card class="w-full max-w-[1920px] p-5">
|
||||||
<mat-card-header>
|
<mat-card-header>
|
||||||
<mat-card-title>{{ 'FRACTAL.TITLE' | translate }}</mat-card-title>
|
<mat-card-title>{{ 'FRACTAL.TITLE' | translate }}</mat-card-title>
|
||||||
</mat-card-header>
|
</mat-card-header>
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<app-information [algorithmInformation]="algoInformation"/>
|
<app-information [algorithmInformation]="algoInformation"/>
|
||||||
<div class="controls-container">
|
<div class="flex flex-col mb-4">
|
||||||
<div class="controls-panel">
|
<div class="flex gap-4 mb-4 items-center flex-wrap mt-2.5 text-[0.9em]">
|
||||||
<mat-form-field appearance="fill">
|
<mat-form-field class="w-[200px]" appearance="fill">
|
||||||
<mat-label>{{ 'FRACTAL.ALGORITHM' | translate }}</mat-label>
|
<mat-label>{{ 'FRACTAL.ALGORITHM' | translate }}</mat-label>
|
||||||
<mat-select [value]="'Mandelbrot'" (selectionChange)="onAlgorithmChange($event.value)">
|
<mat-select [value]="'Mandelbrot'" (selectionChange)="onAlgorithmChange($event.value)">
|
||||||
<mat-option value="Mandelbrot">Mandelbrot</mat-option>
|
<mat-option value="Mandelbrot">Mandelbrot</mat-option>
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
<mat-option value="Newton">Newton</mat-option>
|
<mat-option value="Newton">Newton</mat-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field appearance="fill">
|
<mat-form-field class="w-[200px]" appearance="fill">
|
||||||
<mat-label>{{ 'FRACTAL.COLOR_SCHEME' | translate }}</mat-label>
|
<mat-label>{{ 'FRACTAL.COLOR_SCHEME' | translate }}</mat-label>
|
||||||
<mat-select [value]="'Blue-Gold'" (selectionChange)="onColorChanged($event.value)">
|
<mat-select [value]="'Blue-Gold'" (selectionChange)="onColorChanged($event.value)">
|
||||||
<mat-option value="Blue-Gold">Blue-Gold</mat-option>
|
<mat-option value="Blue-Gold">Blue-Gold</mat-option>
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
<mat-icon>undo</mat-icon> {{ 'FRACTAL.RESET' | translate }}
|
<mat-icon>undo</mat-icon> {{ 'FRACTAL.RESET' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="zoom-controls" style="display: flex; align-items: center; gap: 10px;">
|
<div style="display: flex; align-items: center; gap: 10px;">
|
||||||
<mat-icon>zoom_out</mat-icon>
|
<mat-icon>zoom_out</mat-icon>
|
||||||
<ngx-slider [(value)]="sliderValue" [options]="options" (valueChange)="onSliderChange($event)" ></ngx-slider>
|
<ngx-slider [(value)]="sliderValue" [options]="options" (valueChange)="onSliderChange($event)" ></ngx-slider>
|
||||||
<mat-icon>zoom_in</mat-icon>
|
<mat-icon>zoom_in</mat-icon>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<mat-card class="algo-container">
|
<mat-card class="w-full max-w-[1920px] p-5">
|
||||||
<mat-card-header>
|
<mat-card-header>
|
||||||
<mat-card-title>{{ 'FRACTAL3D.TITLE' | translate }}</mat-card-title>
|
<mat-card-title>{{ 'FRACTAL3D.TITLE' | translate }}</mat-card-title>
|
||||||
</mat-card-header>
|
</mat-card-header>
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<app-information [algorithmInformation]="algoInformation"/>
|
<app-information [algorithmInformation]="algoInformation"/>
|
||||||
<div class="controls-container">
|
<div class="flex flex-col mb-4">
|
||||||
<div class="controls-panel">
|
<div class="flex gap-4 mb-4 items-center flex-wrap mt-2.5 text-[0.9em]">
|
||||||
<button matButton="filled" (click)="onFractalTypeChange(0)">{{ 'FRACTAL3D.MANDELBULB' | translate }}</button>
|
<button matButton="filled" (click)="onFractalTypeChange(0)">{{ 'FRACTAL3D.MANDELBULB' | translate }}</button>
|
||||||
<button matButton="filled" (click)="onFractalTypeChange(1)">{{ 'FRACTAL3D.MANDELBOX' | translate }}</button>
|
<button matButton="filled" (click)="onFractalTypeChange(1)">{{ 'FRACTAL3D.MANDELBOX' | translate }}</button>
|
||||||
<button matButton="filled" (click)="onFractalTypeChange(2)">{{ 'FRACTAL3D.JULIA' | translate }}</button>
|
<button matButton="filled" (click)="onFractalTypeChange(2)">{{ 'FRACTAL3D.JULIA' | translate }}</button>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<div class="algo-info">
|
<div class="mb-4 py-3 px-4 border border-[#ddd] rounded-lg">
|
||||||
<h3>{{ algorithmInformation.title | translate }}</h3>
|
<h3 class="m-0 mb-2">{{ algorithmInformation.title | translate }}</h3>
|
||||||
|
|
||||||
@if(algorithmInformation.entries && algorithmInformation.entries.length > 0){
|
@if(algorithmInformation.entries && algorithmInformation.entries.length > 0){
|
||||||
@for (algo of algorithmInformation.entries; track algo)
|
@for (algo of algorithmInformation.entries; track algo)
|
||||||
{
|
{
|
||||||
<p>
|
<p class="my-2">
|
||||||
<strong>
|
<strong>
|
||||||
@if(algo.translateName){
|
@if(algo.translateName){
|
||||||
{{ algo.name | translate}}
|
{{ algo.name | translate}}
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
@if (algorithmInformation.disclaimer !== '')
|
@if (algorithmInformation.disclaimer !== '')
|
||||||
{
|
{
|
||||||
<p>
|
<p class="my-2">
|
||||||
<strong>{{ 'ALGORITHM.NOTE' | translate}}</strong> {{ algorithmInformation.disclaimer | translate}}
|
<strong>{{ 'ALGORITHM.NOTE' | translate}}</strong> {{ algorithmInformation.disclaimer | translate}}
|
||||||
</p>
|
</p>
|
||||||
@if (algorithmInformation.disclaimerListEntry && algorithmInformation.disclaimerListEntry.length > 0)
|
@if (algorithmInformation.disclaimerListEntry && algorithmInformation.disclaimerListEntry.length > 0)
|
||||||
|
|||||||
@@ -1,27 +1,27 @@
|
|||||||
<mat-card class="algo-container">
|
<mat-card class="w-full max-w-[1920px] p-5">
|
||||||
<mat-card-header>
|
<mat-card-header>
|
||||||
<mat-card-title>{{ 'LABYRINTH.TITLE' | translate }}</mat-card-title>
|
<mat-card-title>{{ 'LABYRINTH.TITLE' | translate }}</mat-card-title>
|
||||||
</mat-card-header>
|
</mat-card-header>
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<app-information [algorithmInformation]="algoInformation"/>
|
<app-information [algorithmInformation]="algoInformation"/>
|
||||||
<div class="controls-container">
|
<div class="flex flex-col mb-4">
|
||||||
<div class="controls-panel">
|
<div class="flex gap-4 mb-4 items-center flex-wrap mt-2.5 text-[0.9em]">
|
||||||
<button matButton="filled" [disabled]="isAnimationRunning()" (click)="visualize('dijkstra')">{{ 'PATHFINDING.DIJKSTRA' | translate }}</button>
|
<button matButton="filled" [disabled]="isAnimationRunning()" (click)="visualize('dijkstra')">{{ 'PATHFINDING.DIJKSTRA' | translate }}</button>
|
||||||
<button matButton="filled" [disabled]="isAnimationRunning()" (click)="visualize('astar')">{{ 'PATHFINDING.ASTAR' | translate }}</button>
|
<button matButton="filled" [disabled]="isAnimationRunning()" (click)="visualize('astar')">{{ 'PATHFINDING.ASTAR' | translate }}</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="controls-panel">
|
<div class="flex gap-4 mb-4 items-center flex-wrap mt-2.5 text-[0.9em]">
|
||||||
<button matButton="filled" [disabled]="isAnimationRunning()" (click)="createRandom(true)">{{ 'LABYRINTH.PRIM' | translate }}</button>
|
<button matButton="filled" [disabled]="isAnimationRunning()" (click)="createRandom(true)">{{ 'LABYRINTH.PRIM' | translate }}</button>
|
||||||
<button matButton="filled" [disabled]="isAnimationRunning()" (click)="createRandom(false)">{{ 'LABYRINTH.KRUSKAL' | translate }}</button>
|
<button matButton="filled" [disabled]="isAnimationRunning()" (click)="createRandom(false)">{{ 'LABYRINTH.KRUSKAL' | translate }}</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="legend">
|
<div class="flex flex-wrap gap-4 items-center text-[0.9em]">
|
||||||
<span><span class="legend-color start"></span> {{ 'PATHFINDING.START_NODE' | translate }}</span>
|
<span><span class="legend-swatch bg-green-600"></span> {{ 'PATHFINDING.START_NODE' | translate }}</span>
|
||||||
<span><span class="legend-color end"></span> {{ 'PATHFINDING.END_NODE' | translate }}</span>
|
<span><span class="legend-swatch bg-red-600"></span> {{ 'PATHFINDING.END_NODE' | translate }}</span>
|
||||||
<span><span class="legend-color wall"></span> {{ 'PATHFINDING.WALL' | translate }}</span>
|
<span><span class="legend-swatch bg-black"></span> {{ 'PATHFINDING.WALL' | translate }}</span>
|
||||||
<span><span class="legend-color visited"></span> {{ 'PATHFINDING.VISITED' | translate }}</span>
|
<span><span class="legend-swatch bg-sky-300"></span> {{ 'PATHFINDING.VISITED' | translate }}</span>
|
||||||
<span><span class="legend-color path"></span> {{ 'PATHFINDING.PATH' | translate }}</span>
|
<span><span class="legend-swatch bg-[gold]"></span> {{ 'PATHFINDING.PATH' | translate }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="controls-panel">
|
<div class="flex gap-4 mb-4 items-center flex-wrap mt-2.5 text-[0.9em]">
|
||||||
<p>{{ 'PATHFINDING.PATH_LENGTH' | translate }}: {{ pathLength }}</p>
|
<p>{{ 'PATHFINDING.PATH_LENGTH' | translate }}: {{ pathLength }}</p>
|
||||||
<p>{{ 'PATHFINDING.EXECUTION_TIME' | translate }}: {{ executionTime | number:'1.2-2' }} ms</p>
|
<p>{{ 'PATHFINDING.EXECUTION_TIME' | translate }}: {{ executionTime | number:'1.2-2' }} ms</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,33 +1,33 @@
|
|||||||
<mat-card class="algo-container">
|
<mat-card class="w-full max-w-[1920px] p-5">
|
||||||
<mat-card-header>
|
<mat-card-header>
|
||||||
<mat-card-title>{{ 'PATHFINDING.TITLE' | translate }}</mat-card-title>
|
<mat-card-title>{{ 'PATHFINDING.TITLE' | translate }}</mat-card-title>
|
||||||
</mat-card-header>
|
</mat-card-header>
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<app-information [algorithmInformation]="algoInformation"/>
|
<app-information [algorithmInformation]="algoInformation"/>
|
||||||
|
|
||||||
<div class="controls-container">
|
<div class="flex flex-col mb-4">
|
||||||
<div class="controls-panel">
|
<div class="flex gap-4 mb-4 items-center flex-wrap mt-2.5 text-[0.9em]">
|
||||||
<button matButton="filled" (click)="visualize('dijkstra')">{{ 'PATHFINDING.DIJKSTRA' | translate }}</button>
|
<button matButton="filled" (click)="visualize('dijkstra')">{{ 'PATHFINDING.DIJKSTRA' | translate }}</button>
|
||||||
<button matButton="filled" (click)="visualize('astar')">{{ 'PATHFINDING.ASTAR' | translate }}</button>
|
<button matButton="filled" (click)="visualize('astar')">{{ 'PATHFINDING.ASTAR' | translate }}</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="controls-panel">
|
<div class="flex gap-4 mb-4 items-center flex-wrap mt-2.5 text-[0.9em]">
|
||||||
<button matButton="filled" (click)="createCase({withWalls: true, scenario: 'normal'})">{{ 'PATHFINDING.NORMAL_CASE' | translate }}</button>
|
<button matButton="filled" (click)="createCase({withWalls: true, scenario: 'normal'})">{{ 'PATHFINDING.NORMAL_CASE' | translate }}</button>
|
||||||
<button matButton="filled" (click)="createCase({withWalls: true, scenario: 'random'})">{{ 'PATHFINDING.RANDOM_CASE' | translate }}</button>
|
<button matButton="filled" (click)="createCase({withWalls: true, scenario: 'random'})">{{ 'PATHFINDING.RANDOM_CASE' | translate }}</button>
|
||||||
<button matButton="filled" (click)="createCase({withWalls: true, scenario: 'edge'})">{{ 'PATHFINDING.EDGE_CASE' | translate }}</button>
|
<button matButton="filled" (click)="createCase({withWalls: true, scenario: 'edge'})">{{ 'PATHFINDING.EDGE_CASE' | translate }}</button>
|
||||||
<button matButton="filled" (click)="createCase({withWalls: false, scenario: 'normal'})">{{ 'PATHFINDING.CLEAR_BOARD' | translate }}</button>
|
<button matButton="filled" (click)="createCase({withWalls: false, scenario: 'normal'})">{{ 'PATHFINDING.CLEAR_BOARD' | translate }}</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="controls-panel">
|
<div class="flex gap-4 mb-4 items-center flex-wrap mt-2.5 text-[0.9em]">
|
||||||
<mat-button-toggle-group [(ngModel)]="selectedNodeType" aria-label="Node Type Selection">
|
<mat-button-toggle-group class="rounded overflow-hidden" [(ngModel)]="selectedNodeType" aria-label="Node Type Selection">
|
||||||
<mat-button-toggle [value]="NodeType.Start">{{ 'PATHFINDING.START_NODE' | translate }}</mat-button-toggle>
|
<mat-button-toggle [value]="NodeType.Start">{{ 'PATHFINDING.START_NODE' | translate }}</mat-button-toggle>
|
||||||
<mat-button-toggle [value]="NodeType.End">{{ 'PATHFINDING.END_NODE' | translate }}</mat-button-toggle>
|
<mat-button-toggle [value]="NodeType.End">{{ 'PATHFINDING.END_NODE' | translate }}</mat-button-toggle>
|
||||||
<mat-button-toggle [value]="NodeType.Wall">{{ 'PATHFINDING.WALL' | translate }}</mat-button-toggle>
|
<mat-button-toggle [value]="NodeType.Wall">{{ 'PATHFINDING.WALL' | translate }}</mat-button-toggle>
|
||||||
<mat-button-toggle [value]="NodeType.None">{{ 'PATHFINDING.CLEAR_NODE' | translate }}</mat-button-toggle>
|
<mat-button-toggle [value]="NodeType.None">{{ 'PATHFINDING.CLEAR_NODE' | translate }}</mat-button-toggle>
|
||||||
</mat-button-toggle-group>
|
</mat-button-toggle-group>
|
||||||
</div>
|
</div>
|
||||||
<div class="controls-panel">
|
<div class="flex gap-4 mb-4 items-center flex-wrap mt-2.5 text-[0.9em]">
|
||||||
<div class="input-container">
|
<div class="flex gap-3 items-center flex-wrap">
|
||||||
<mat-form-field appearance="outline" class="input-field">
|
<mat-form-field appearance="outline" class="w-[150px]">
|
||||||
<mat-label>{{ 'ALGORITHM.GRID_HEIGHT' | translate }}</mat-label>
|
<mat-label>{{ 'ALGORITHM.GRID_HEIGHT' | translate }}</mat-label>
|
||||||
<input
|
<input
|
||||||
matInput
|
matInput
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
(ngModelChange)="genericGridComponent.gridRows = gridRows; genericGridComponent.applyGridSize()"
|
(ngModelChange)="genericGridComponent.gridRows = gridRows; genericGridComponent.applyGridSize()"
|
||||||
/> </mat-form-field>
|
/> </mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="input-field">
|
<mat-form-field appearance="outline" class="w-[150px]">
|
||||||
<mat-label>{{ 'ALGORITHM.GRID_WIDTH' | translate }}</mat-label>
|
<mat-label>{{ 'ALGORITHM.GRID_WIDTH' | translate }}</mat-label>
|
||||||
<input
|
<input
|
||||||
matInput
|
matInput
|
||||||
@@ -51,14 +51,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="legend">
|
<div class="flex flex-wrap gap-4 items-center text-[0.9em]">
|
||||||
<span><span class="legend-color start"></span> {{ 'PATHFINDING.START_NODE' | translate }}</span>
|
<span><span class="legend-swatch bg-green-600"></span> {{ 'PATHFINDING.START_NODE' | translate }}</span>
|
||||||
<span><span class="legend-color end"></span> {{ 'PATHFINDING.END_NODE' | translate }}</span>
|
<span><span class="legend-swatch bg-red-600"></span> {{ 'PATHFINDING.END_NODE' | translate }}</span>
|
||||||
<span><span class="legend-color wall"></span> {{ 'PATHFINDING.WALL' | translate }}</span>
|
<span><span class="legend-swatch bg-black"></span> {{ 'PATHFINDING.WALL' | translate }}</span>
|
||||||
<span><span class="legend-color visited"></span> {{ 'PATHFINDING.VISITED' | translate }}</span>
|
<span><span class="legend-swatch bg-sky-300"></span> {{ 'PATHFINDING.VISITED' | translate }}</span>
|
||||||
<span><span class="legend-color path"></span> {{ 'PATHFINDING.PATH' | translate }}</span>
|
<span><span class="legend-swatch bg-[gold]"></span> {{ 'PATHFINDING.PATH' | translate }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="controls-panel">
|
<div class="flex gap-4 mb-4 items-center flex-wrap mt-2.5 text-[0.9em]">
|
||||||
<p>{{ 'PATHFINDING.PATH_LENGTH' | translate }}: {{ pathLength }}</p>
|
<p>{{ 'PATHFINDING.PATH_LENGTH' | translate }}: {{ pathLength }}</p>
|
||||||
<p>{{ 'PATHFINDING.EXECUTION_TIME' | translate }}: {{ executionTime | number:'1.2-2' }} ms</p>
|
<p>{{ 'PATHFINDING.EXECUTION_TIME' | translate }}: {{ executionTime | number:'1.2-2' }} ms</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,45 +1,45 @@
|
|||||||
<mat-card class="algo-container">
|
<mat-card class="w-full max-w-[1920px] p-5">
|
||||||
<mat-card-header>
|
<mat-card-header>
|
||||||
<mat-card-title>{{ 'PENDULUM.TITLE' | translate }}</mat-card-title>
|
<mat-card-title>{{ 'PENDULUM.TITLE' | translate }}</mat-card-title>
|
||||||
</mat-card-header>
|
</mat-card-header>
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<app-information [algorithmInformation]="algoInformation"/>
|
<app-information [algorithmInformation]="algoInformation"/>
|
||||||
<div class="controls-container">
|
<div class="flex flex-col mb-4">
|
||||||
<div class="sliders-grid">
|
<div class="grid grid-cols-2 tablet:grid-cols-1 tablet:gap-4 mb-6">
|
||||||
<div class="slider-item">
|
<div class="flex items-center gap-4 mr-4">
|
||||||
<p>{{ 'PENDULUM.TRAIL_DECAY_TIME' | translate }}</p>
|
<p class="w-[100px] shrink-0 m-0 text-sm">{{ 'PENDULUM.TRAIL_DECAY_TIME' | translate }}</p>
|
||||||
<ngx-slider [(value)]="simParams.trailDecay" [options]="trailDecayOptions" ></ngx-slider>
|
<ngx-slider class="grow" [(value)]="simParams.trailDecay" [options]="trailDecayOptions" ></ngx-slider>
|
||||||
</div>
|
</div>
|
||||||
<div class="slider-item">
|
<div class="flex items-center gap-4 mr-4">
|
||||||
<p>{{ 'PENDULUM.ATTRACTION' | translate }}</p>
|
<p class="w-[100px] shrink-0 m-0 text-sm">{{ 'PENDULUM.ATTRACTION' | translate }}</p>
|
||||||
<ngx-slider [(value)]="simParams.g" [options]="gravityOptions" ></ngx-slider>
|
<ngx-slider class="grow" [(value)]="simParams.g" [options]="gravityOptions" ></ngx-slider>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="slider-item">
|
<div class="flex items-center gap-4 mr-4">
|
||||||
<p>{{ 'PENDULUM.L1_LENGTH' | translate }}</p>
|
<p class="w-[100px] shrink-0 m-0 text-sm">{{ 'PENDULUM.L1_LENGTH' | translate }}</p>
|
||||||
<ngx-slider [(value)]="simParams.l1" [options]="lengthOptions" ></ngx-slider>
|
<ngx-slider class="grow" [(value)]="simParams.l1" [options]="lengthOptions" ></ngx-slider>
|
||||||
</div>
|
</div>
|
||||||
<div class="slider-item">
|
<div class="flex items-center gap-4 mr-4">
|
||||||
<p>{{ 'PENDULUM.L2_LENGTH' | translate }}</p>
|
<p class="w-[100px] shrink-0 m-0 text-sm">{{ 'PENDULUM.L2_LENGTH' | translate }}</p>
|
||||||
<ngx-slider [(value)]="simParams.l2" [options]="lengthOptions" ></ngx-slider>
|
<ngx-slider class="grow" [(value)]="simParams.l2" [options]="lengthOptions" ></ngx-slider>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="slider-item">
|
<div class="flex items-center gap-4 mr-4">
|
||||||
<p>{{ 'PENDULUM.M1_MASS' | translate }}</p>
|
<p class="w-[100px] shrink-0 m-0 text-sm">{{ 'PENDULUM.M1_MASS' | translate }}</p>
|
||||||
<ngx-slider [(value)]="simParams.m1" [options]="massOptions" ></ngx-slider>
|
<ngx-slider class="grow" [(value)]="simParams.m1" [options]="massOptions" ></ngx-slider>
|
||||||
</div>
|
</div>
|
||||||
<div class="slider-item">
|
<div class="flex items-center gap-4 mr-4">
|
||||||
<p>{{ 'PENDULUM.M2_MASS' | translate }}</p>
|
<p class="w-[100px] shrink-0 m-0 text-sm">{{ 'PENDULUM.M2_MASS' | translate }}</p>
|
||||||
<ngx-slider [(value)]="simParams.m2" [options]="massOptions" ></ngx-slider>
|
<ngx-slider class="grow" [(value)]="simParams.m2" [options]="massOptions" ></ngx-slider>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="slider-item full-width">
|
<div class="flex items-center gap-4 mr-4 col-span-full">
|
||||||
<p>{{ 'PENDULUM.DAMPING' | translate }}</p>
|
<p class="w-[100px] shrink-0 m-0 text-sm">{{ 'PENDULUM.DAMPING' | translate }}</p>
|
||||||
<ngx-slider [(value)]="simParams.damping" [options]="dampingOptions" ></ngx-slider>
|
<ngx-slider class="grow" [(value)]="simParams.damping" [options]="dampingOptions" ></ngx-slider>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="actions-container">
|
<div class="flex flex-wrap gap-3 mb-4">
|
||||||
<button mat-raised-button color="primary" (click)="pushPendulum(true)">
|
<button mat-raised-button color="primary" (click)="pushPendulum(true)">
|
||||||
{{ 'PENDULUM.POKE_M1' | translate }}
|
{{ 'PENDULUM.POKE_M1' | translate }}
|
||||||
</button>
|
</button>
|
||||||
@@ -51,11 +51,11 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="legend" style="margin-top: 10px">
|
<div class="flex flex-wrap gap-4 items-center text-[0.9em] mt-2.5">
|
||||||
<span><span class="legend-color L1"></span> L1</span>
|
<span><span class="legend-swatch bg-yellow-400"></span> L1</span>
|
||||||
<span><span class="legend-color L2"></span> L2</span>
|
<span><span class="legend-swatch bg-fuchsia-500"></span> L2</span>
|
||||||
<span><span class="legend-color M1"></span> M1</span>
|
<span><span class="legend-swatch bg-red-600"></span> M1</span>
|
||||||
<span><span class="legend-color M2"></span> M2</span>
|
<span><span class="legend-swatch bg-green-600"></span> M2</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<app-babylon-canvas
|
<app-babylon-canvas
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
.sliders-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
|
|
||||||
.slider-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1rem;
|
|
||||||
margin-right: 1rem;
|
|
||||||
|
|
||||||
p {
|
|
||||||
width: 100px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
margin: 0;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
ngx-slider {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.full-width {
|
|
||||||
grid-column: 1 / -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.actions-container {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.75rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 900px) {
|
|
||||||
.sliders-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<mat-card class="algo-container sorting-card">
|
<mat-card class="w-full max-w-[1920px] p-[clamp(10px,3vw,20px)]">
|
||||||
<mat-card-header>
|
<mat-card-header>
|
||||||
<mat-card-title>{{ 'SORTING.TITLE' | translate }}</mat-card-title>
|
<mat-card-title>{{ 'SORTING.TITLE' | translate }}</mat-card-title>
|
||||||
</mat-card-header>
|
</mat-card-header>
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<app-information [algorithmInformation]="algoInformation"/>
|
<app-information [algorithmInformation]="algoInformation"/>
|
||||||
<div class="controls-panel">
|
<div class="flex gap-[clamp(5px,2vw,10px)] mb-[clamp(10px,3vw,20px)] items-center flex-wrap mt-2.5 text-[0.9em]">
|
||||||
<mat-form-field appearance="fill">
|
<mat-form-field class="w-[clamp(150px,20vw,200px)]" appearance="fill">
|
||||||
<mat-label>{{ 'SORTING.ALGORITHM' | translate }}</mat-label>
|
<mat-label>{{ 'SORTING.ALGORITHM' | translate }}</mat-label>
|
||||||
<mat-select [(ngModel)]="selectedAlgorithm">
|
<mat-select [(ngModel)]="selectedAlgorithm">
|
||||||
@for (algo of algoInformation.entries; track algo.name) {
|
@for (algo of algoInformation.entries; track algo.name) {
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field class="w-[clamp(150px,20vw,200px)]" appearance="outline">
|
||||||
<mat-label>{{ 'SORTING.ARRAY_SIZE' | translate }}</mat-label>
|
<mat-label>{{ 'SORTING.ARRAY_SIZE' | translate }}</mat-label>
|
||||||
<input
|
<input
|
||||||
matInput
|
matInput
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
/>
|
/>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
<div class="controls-panel">
|
<div class="flex gap-[clamp(5px,2vw,10px)] mb-[clamp(10px,3vw,20px)] items-center flex-wrap">
|
||||||
<button mat-raised-button color="primary" (click)="startSorting()">
|
<button mat-raised-button color="primary" (click)="startSorting()">
|
||||||
<mat-icon>play_arrow</mat-icon> {{ 'SORTING.START' | translate }}
|
<mat-icon>play_arrow</mat-icon> {{ 'SORTING.START' | translate }}
|
||||||
</button>
|
</button>
|
||||||
@@ -43,17 +43,19 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="controls-panel">
|
<div class="flex gap-4 mb-4 items-center flex-wrap mt-2.5 text-[0.9em]">
|
||||||
<p>{{ 'SORTING.EXECUTION_TIME' | translate }}: {{ executionTime }} ms</p>
|
<p>{{ 'SORTING.EXECUTION_TIME' | translate }}: {{ executionTime }} ms</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="visualization-area">
|
<div class="flex items-end h-[clamp(200px,40vh,400px)] border-b border-app-fg mb-[clamp(10px,3vw,20px)] gap-px bg-card-bg">
|
||||||
@for (item of sortArray; track $index) {
|
@for (item of sortArray; track $index) {
|
||||||
<div
|
<div
|
||||||
class="bar"
|
class="grow w-2.5 min-w-px transition-all duration-[50ms]"
|
||||||
[style.height.px]="item.value * 3"
|
[style.height.px]="item.value * 3"
|
||||||
[class.unsorted]="item.state === 'unsorted'"
|
[ngClass]="{
|
||||||
[class.comparing]="item.state === 'comparing'"
|
'bg-[#424242]': item.state === 'unsorted',
|
||||||
[class.sorted]="item.state === 'sorted'"
|
'bg-[#ffeb3b]': item.state === 'comparing',
|
||||||
|
'bg-[#4caf50]': item.state === 'sorted'
|
||||||
|
}"
|
||||||
></div>
|
></div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<section class="imprint">
|
<section class="grid gap-fluid-md max-w-app mx-4 mt-auto">
|
||||||
<mat-card class="imprint-card">
|
<mat-card class="p-fluid-md">
|
||||||
<h2 class="imprint-title">{{ 'IMPRINT.TITLE' | translate }}</h2>
|
<h2 class="m-0 mb-4 text-[clamp(1rem,3vw,1.2rem)] font-semibold">{{ 'IMPRINT.TITLE' | translate }}</h2>
|
||||||
|
|
||||||
<div class="imprint-section">
|
<div class="grid gap-1 mb-4">
|
||||||
<p class="imprint-label">{{ 'IMPRINT.PARAGRAPH' | translate }}</p>
|
<p class="text-xs tracking-[0.04em] uppercase opacity-70 m-0">{{ 'IMPRINT.PARAGRAPH' | translate }}</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Andreas Dahm<br />
|
Andreas Dahm<br />
|
||||||
@@ -13,8 +13,8 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="imprint-section">
|
<div class="grid gap-1 mb-4">
|
||||||
<p class="imprint-label">{{ 'IMPRINT.CONTACT' | translate }}</p>
|
<p class="text-xs tracking-[0.04em] uppercase opacity-70 m-0">{{ 'IMPRINT.CONTACT' | translate }}</p>
|
||||||
<p>
|
<p>
|
||||||
E-Mail:
|
E-Mail:
|
||||||
<a href="mailto:andreas.dahm@gmail.com">
|
<a href="mailto:andreas.dahm@gmail.com">
|
||||||
|
|||||||
@@ -1,38 +1,38 @@
|
|||||||
<h2 mat-dialog-title>{{ project.title | translate }}</h2>
|
<h2 mat-dialog-title>{{ project.title | translate }}</h2>
|
||||||
<mat-dialog-content #dialogContent>
|
<mat-dialog-content #dialogContent>
|
||||||
<div class="project-dialog-layout">
|
<div class="flex flex-col gap-6">
|
||||||
<div class="project-info">
|
<div>
|
||||||
<p class="introduction">{{ project.introduction | translate }}</p>
|
<p class="text-lg leading-relaxed opacity-90 mb-4">{{ project.introduction | translate }}</p>
|
||||||
|
|
||||||
<div class="features-list">
|
<div class="mb-6">
|
||||||
<ul>
|
<ul class="pl-5">
|
||||||
@for(bullet of project.bulletPoints; track bullet) {
|
@for(bullet of project.bulletPoints; track bullet) {
|
||||||
<li>{{ bullet | translate }}</li>
|
<li class="mb-2">{{ bullet | translate }}</li>
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="insight-grid">
|
<div class="grid grid-cols-[repeat(auto-fit,minmax(300px,1fr))] sm-dialog:grid-cols-1 gap-4 mb-4">
|
||||||
<div class="insight-card technical">
|
<div class="p-5 rounded-xl bg-black/[.03] border border-black/[.05] dark:bg-white/[.05] dark:border-white/10">
|
||||||
<div class="insight-header">
|
<div class="flex items-center gap-3 mb-3 text-link">
|
||||||
<mat-icon>settings_suggest</mat-icon>
|
<mat-icon class="!text-[24px] !w-6 !h-6">settings_suggest</mat-icon>
|
||||||
<h3>{{ 'PROJECTS.SECTION.TECHNICAL' | translate }}</h3>
|
<h3 class="m-0 text-sm uppercase tracking-wider font-semibold">{{ 'PROJECTS.SECTION.TECHNICAL' | translate }}</h3>
|
||||||
</div>
|
</div>
|
||||||
<ul>
|
<ul class="m-0 pl-5 text-[0.95rem] leading-relaxed opacity-85">
|
||||||
@for(challenge of project.challenges; track challenge) {
|
@for(challenge of project.challenges; track challenge) {
|
||||||
<li>{{ challenge | translate }}</li>
|
<li class="mb-1">{{ challenge | translate }}</li>
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="insight-card softskills">
|
<div class="p-5 rounded-xl bg-black/[.03] border border-black/[.05] dark:bg-white/[.05] dark:border-white/10">
|
||||||
<div class="insight-header">
|
<div class="flex items-center gap-3 mb-3 text-link">
|
||||||
<mat-icon>psychology</mat-icon>
|
<mat-icon class="!text-[24px] !w-6 !h-6">psychology</mat-icon>
|
||||||
<h3>{{ 'PROJECTS.SECTION.LEARNINGS' | translate }}</h3>
|
<h3 class="m-0 text-sm uppercase tracking-wider font-semibold">{{ 'PROJECTS.SECTION.LEARNINGS' | translate }}</h3>
|
||||||
</div>
|
</div>
|
||||||
<ul>
|
<ul class="m-0 pl-5 text-[0.95rem] leading-relaxed opacity-85">
|
||||||
@for(learning of project.learnings; track learning) {
|
@for(learning of project.learnings; track learning) {
|
||||||
<li>{{ learning | translate }}</li>
|
<li class="mb-1">{{ learning | translate }}</li>
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -41,14 +41,14 @@
|
|||||||
|
|
||||||
@if (project.images.length > 0)
|
@if (project.images.length > 0)
|
||||||
{
|
{
|
||||||
<div class="media-section">
|
<div class="my-4 rounded-xl overflow-hidden bg-black">
|
||||||
<swiper-container class="my-swiper" [attr.slides-per-view]="1" [attr.space-between]="12" [attr.navigation]="true"
|
<swiper-container class="my-swiper rounded-xl" [attr.slides-per-view]="1" [attr.space-between]="12" [attr.navigation]="true"
|
||||||
[attr.pagination]="true" [attr.keyboard]="true" style="width: 100%;">
|
[attr.pagination]="true" [attr.keyboard]="true" style="width: 100%;">
|
||||||
@for (img of project.images; track img) {
|
@for (img of project.images; track img) {
|
||||||
<swiper-slide>
|
<swiper-slide class="rounded-xl overflow-hidden flex flex-col bg-[#222]">
|
||||||
<img class="slide-img" [src]="img.url" [alt]="project.title | translate" />
|
<img class="w-full h-auto !max-h-[clamp(300px,60vh,512px)] object-contain block shrink-0" [src]="img.url" [alt]="project.title | translate" />
|
||||||
@if (img.source) {
|
@if (img.source) {
|
||||||
<div class="slide-source">
|
<div class="text-xs text-[#aaa] bg-[#2a2a2a] p-2 text-right border-t border-[#444]">
|
||||||
{{ img.source }}
|
{{ img.source }}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -58,8 +58,8 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="footer-details">
|
<div class="flex flex-col gap-4 pt-4 border-t border-black/10 dark:border-white/10">
|
||||||
<div class="tech-stack">
|
<div class="flex flex-wrap">
|
||||||
<mat-chip-set aria-label="Technologies">
|
<mat-chip-set aria-label="Technologies">
|
||||||
@for(tech of project.technologies; track tech) {
|
@for(tech of project.technologies; track tech) {
|
||||||
<mat-chip>{{tech}}</mat-chip>
|
<mat-chip>{{tech}}</mat-chip>
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
</mat-chip-set>
|
</mat-chip-set>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="link-section">
|
<div class="flex flex-wrap gap-fluid-sm mt-6">
|
||||||
@for(link of project.links; track link)
|
@for(link of project.links; track link)
|
||||||
{
|
{
|
||||||
<a mat-button href="{{link.url}}" target="_blank" rel="noopener noreferrer">
|
<a mat-button href="{{link.url}}" target="_blank" rel="noopener noreferrer">
|
||||||
|
|||||||
@@ -1,117 +0,0 @@
|
|||||||
.project-dialog-layout {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.introduction {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
line-height: 1.6;
|
|
||||||
opacity: 0.9;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.features-list {
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
ul {
|
|
||||||
padding-left: 1.2rem;
|
|
||||||
li {
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.insight-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
||||||
gap: 1rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.insight-card {
|
|
||||||
padding: 1.25rem;
|
|
||||||
border-radius: 12px;
|
|
||||||
background: rgba(0, 0, 0, 0.03);
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
|
||||||
|
|
||||||
.insight-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.75rem;
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
color: var(--link-color);
|
|
||||||
|
|
||||||
mat-icon {
|
|
||||||
font-size: 24px;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
margin: 0;
|
|
||||||
padding-left: 1.2rem;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
line-height: 1.5;
|
|
||||||
opacity: 0.85;
|
|
||||||
|
|
||||||
li {
|
|
||||||
margin-bottom: 0.4rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark .insight-card {
|
|
||||||
background: rgba(255, 255, 255, 0.05);
|
|
||||||
border-color: rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.media-section {
|
|
||||||
margin: 1rem 0;
|
|
||||||
border-radius: 12px;
|
|
||||||
overflow: hidden;
|
|
||||||
background: #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-details {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1rem;
|
|
||||||
padding-top: 1rem;
|
|
||||||
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark .footer-details {
|
|
||||||
border-top-color: rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tech-stack {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.link-section {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.5rem;
|
|
||||||
|
|
||||||
a {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
|
||||||
.insight-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,52 +1,52 @@
|
|||||||
<div class="card-grid">
|
<div class="grid gap-fluid-md grid-cols-[repeat(auto-fill,minmax(min(100%,450px),1fr))] max-w-app mx-4 mt-auto">
|
||||||
@if (featuredProject(); as project) {
|
@if (featuredProject(); as project) {
|
||||||
<mat-card class="project-card featured">
|
<mat-card class="card-gradient-bar transition-transform duration-200 ease-in-out flex flex-col h-full col-span-full hover:-translate-y-[5px] hover:shadow-[0_4px_20px_rgba(0,0,0,0.15)]">
|
||||||
<mat-card-header>
|
<mat-card-header class="pb-4">
|
||||||
<mat-card-title>{{ project.title | translate }}</mat-card-title>
|
<mat-card-title>{{ project.title | translate }}</mat-card-title>
|
||||||
<mat-card-subtitle>{{ project.shortDescription | translate }}</mat-card-subtitle>
|
<mat-card-subtitle>{{ project.shortDescription | translate }}</mat-card-subtitle>
|
||||||
</mat-card-header>
|
</mat-card-header>
|
||||||
@if(project.images.length > 0) {
|
@if(project.images.length > 0) {
|
||||||
<img mat-card-image [src]="project.images[0].url" [alt]="project.title | translate">
|
<img mat-card-image class="w-full h-[clamp(150px,25vw,250px)] object-cover" [src]="project.images[0].url" [alt]="project.title | translate">
|
||||||
} @else {
|
} @else {
|
||||||
<div class="icon-container">
|
<div class="flex justify-center items-center h-[clamp(150px,20vw,200px)] bg-[#f0f0f0]">
|
||||||
<mat-icon class="fallback-icon">{{ project.icon }}</mat-icon>
|
<mat-icon class="text-[clamp(3rem,8vw,4rem)] w-[clamp(3rem,8vw,4rem)] h-[clamp(3rem,8vw,4rem)] text-[#666]">{{ project.icon }}</mat-icon>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<mat-card-content>
|
<mat-card-content class="grow pt-4 pb-4">
|
||||||
<p>{{ project.introduction | translate }}</p>
|
<p>{{ project.introduction | translate }}</p>
|
||||||
<mat-chip-set aria-label="Technologies">
|
<mat-chip-set class="pt-fluid-sm" aria-label="Technologies">
|
||||||
@for(tech of project.technologies; track tech) {
|
@for(tech of project.technologies; track tech) {
|
||||||
<mat-chip>{{tech}}</mat-chip>
|
<mat-chip>{{tech}}</mat-chip>
|
||||||
}
|
}
|
||||||
</mat-chip-set>
|
</mat-chip-set>
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
<mat-card-actions>
|
<mat-card-actions class="mt-auto">
|
||||||
<button mat-button (click)="openProjectDialog(project)">{{ 'PROJECTS.READ_MORE' | translate }}</button>
|
<button mat-button (click)="openProjectDialog(project)">{{ 'PROJECTS.READ_MORE' | translate }}</button>
|
||||||
</mat-card-actions>
|
</mat-card-actions>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
}
|
}
|
||||||
|
|
||||||
@for (project of otherProjects(); track project) {
|
@for (project of otherProjects(); track project) {
|
||||||
<mat-card class="project-card">
|
<mat-card class="card-gradient-bar transition-transform duration-200 ease-in-out flex flex-col h-full hover:-translate-y-[5px] hover:shadow-[0_4px_20px_rgba(0,0,0,0.15)]">
|
||||||
<mat-card-header>
|
<mat-card-header class="pb-4">
|
||||||
<mat-card-title>{{ project.title | translate }}</mat-card-title>
|
<mat-card-title>{{ project.title | translate }}</mat-card-title>
|
||||||
</mat-card-header>
|
</mat-card-header>
|
||||||
@if(project.images.length > 0) {
|
@if(project.images.length > 0) {
|
||||||
<img mat-card-image [src]="project.images[0].url" [alt]="project.title | translate">
|
<img mat-card-image class="w-full h-[clamp(150px,25vw,250px)] object-cover" [src]="project.images[0].url" [alt]="project.title | translate">
|
||||||
} @else {
|
} @else {
|
||||||
<div class="icon-container">
|
<div class="flex justify-center items-center h-[clamp(150px,20vw,200px)] bg-[#f0f0f0]">
|
||||||
<mat-icon class="fallback-icon">{{ project.icon }}</mat-icon>
|
<mat-icon class="text-[clamp(3rem,8vw,4rem)] w-[clamp(3rem,8vw,4rem)] h-[clamp(3rem,8vw,4rem)] text-[#666]">{{ project.icon }}</mat-icon>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<mat-card-content>
|
<mat-card-content class="grow pt-4 pb-4">
|
||||||
<p>{{ project.shortDescription | translate }}</p>
|
<p>{{ project.shortDescription | translate }}</p>
|
||||||
<mat-chip-set aria-label="Technologies">
|
<mat-chip-set class="pt-fluid-sm" aria-label="Technologies">
|
||||||
@for(tech of project.technologies; track tech) {
|
@for(tech of project.technologies; track tech) {
|
||||||
<mat-chip>{{tech}}</mat-chip>
|
<mat-chip>{{tech}}</mat-chip>
|
||||||
}
|
}
|
||||||
</mat-chip-set>
|
</mat-chip-set>
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
<mat-card-actions>
|
<mat-card-actions class="mt-auto">
|
||||||
<button mat-button (click)="openProjectDialog(project)">{{ 'PROJECTS.READ_MORE' | translate }}</button>
|
<button mat-button (click)="openProjectDialog(project)">{{ 'PROJECTS.READ_MORE' | translate }}</button>
|
||||||
</mat-card-actions>
|
</mat-card-actions>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
<div class="canvas-container">
|
<div class="flex justify-center items-center w-full max-w-[1000px] mx-auto">
|
||||||
<canvas #gridCanvas></canvas>
|
<canvas #gridCanvas
|
||||||
|
class="block w-full h-auto aspect-square min-w-[200px] max-w-[1000px] touch-none rounded-[clamp(10px,2vw,20px)] outline-none">
|
||||||
|
</canvas>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,9 +7,3 @@
|
|||||||
z-index: -1;
|
z-index: -1;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
canvas {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
border-width: 0;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
<div class="canvas-container">
|
<div class="flex justify-center items-center w-full max-w-[1000px] mx-auto">
|
||||||
<canvas #renderCanvas></canvas>
|
<canvas #renderCanvas
|
||||||
|
class="block w-full h-auto aspect-square min-w-[200px] max-w-[1000px] touch-none rounded-[clamp(10px,2vw,20px)] outline-none">
|
||||||
|
</canvas>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,6 +11,6 @@
|
|||||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined&display=swap" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<app-root></app-root>
|
<app-root class="flex flex-col min-h-screen"></app-root>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
784
src/styles.scss
784
src/styles.scss
@@ -13,13 +13,11 @@ $dark-theme: mat.define-theme((color: (theme-type: dark, primary: mat.$cyan-pale
|
|||||||
@include mat.core-theme($light-theme);
|
@include mat.core-theme($light-theme);
|
||||||
@include mat.all-component-themes($light-theme);
|
@include mat.all-component-themes($light-theme);
|
||||||
|
|
||||||
|
|
||||||
// Dark-Mode
|
|
||||||
.dark {
|
.dark {
|
||||||
@include mat.all-component-colors($dark-theme);
|
@include mat.all-component-colors($dark-theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- Custom variables ---- */
|
/* ---- Custom variables (bridge Material tokens to CSS vars for Tailwind) ---- */
|
||||||
:root {
|
:root {
|
||||||
--app-maxWidth: 1200px;
|
--app-maxWidth: 1200px;
|
||||||
--app-bg: #{mat.get-theme-color($light-theme, surface-container-low)};
|
--app-bg: #{mat.get-theme-color($light-theme, surface-container-low)};
|
||||||
@@ -50,37 +48,7 @@ $dark-theme: mat.define-theme((color: (theme-type: dark, primary: mat.$cyan-pale
|
|||||||
--link-color-hover: #9ad2ff;
|
--link-color-hover: #9ad2ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark body {
|
/* ---- Material component theme transitions ---- */
|
||||||
background: radial-gradient(ellipse at 50% 0%, #1e2530 0%, #1a1a1a 65%);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- global background and tests ---- */
|
|
||||||
html,
|
|
||||||
body {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
font-family: Inter, Roboto, Arial, sans-serif;
|
|
||||||
background-color: var(--app-bg);
|
|
||||||
color: var(--app-fg);
|
|
||||||
transition: background-color 220ms ease, color 220ms ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.material-symbols-outlined {
|
|
||||||
font-variation-settings:
|
|
||||||
"FILL" 0,
|
|
||||||
/* 0 oder 1 */
|
|
||||||
"wght" 400,
|
|
||||||
/* 100..700 */
|
|
||||||
"GRAD" 0,
|
|
||||||
/* -50..200 */
|
|
||||||
"opsz" 24;
|
|
||||||
/* 20..48 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* smooth transition between theme change */
|
|
||||||
.mat-toolbar,
|
.mat-toolbar,
|
||||||
.mat-mdc-card,
|
.mat-mdc-card,
|
||||||
.mat-sidenav,
|
.mat-sidenav,
|
||||||
@@ -97,37 +65,14 @@ body {
|
|||||||
fill 220ms ease;
|
fill 220ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* links */
|
/* ---- Material card overrides ---- */
|
||||||
a {
|
|
||||||
color: var(--link-color);
|
|
||||||
text-decoration: none;
|
|
||||||
font-weight: 500;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: var(--link-color-hover);
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* cards */
|
|
||||||
.mat-mdc-card {
|
.mat-mdc-card {
|
||||||
position: relative;
|
position: relative;
|
||||||
border-radius: var(--card-radius) !important;
|
border-radius: var(--card-radius) !important;
|
||||||
background: var(--card-bg) !important;
|
background: var(--card-bg) !important;
|
||||||
box-shadow: var(--card-shadow-outer);
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
border: none;
|
border: none;
|
||||||
|
transition: box-shadow 200ms ease, transform 200ms ease;
|
||||||
transition:
|
|
||||||
box-shadow 200ms ease,
|
|
||||||
transform 200ms ease;
|
|
||||||
|
|
||||||
&.algo-container {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 1920px;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mat-mdc-card::before {
|
.mat-mdc-card::before {
|
||||||
@@ -136,22 +81,12 @@ a {
|
|||||||
inset: 0;
|
inset: 0;
|
||||||
border-radius: inherit;
|
border-radius: inherit;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
||||||
box-shadow:
|
box-shadow:
|
||||||
inset 0 1px 0 var(--card-border-inset),
|
inset 0 1px 0 var(--card-border-inset),
|
||||||
inset 0 -1px 0 var(--card-border-inset-dark);
|
inset 0 -1px 0 var(--card-border-inset-dark);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mat-mdc-card::after {
|
/* ---- Material accordion/expansion panel overrides ---- */
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
inset: -1px;
|
|
||||||
border-radius: inherit;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* accordion */
|
|
||||||
/* expansion panels like cards */
|
|
||||||
.mat-accordion {
|
.mat-accordion {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
@@ -161,31 +96,20 @@ a {
|
|||||||
border-radius: var(--card-radius) !important;
|
border-radius: var(--card-radius) !important;
|
||||||
background: var(--card-bg) !important;
|
background: var(--card-bg) !important;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
/* ok */
|
|
||||||
border: none !important;
|
border: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.mat-accordion .mat-expansion-panel::before {
|
.mat-accordion .mat-expansion-panel::before {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
border-radius: inherit;
|
border-radius: inherit;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
||||||
box-shadow:
|
box-shadow:
|
||||||
inset 0 1px 0 var(--card-border-inset),
|
inset 0 1px 0 var(--card-border-inset),
|
||||||
inset 0 -1px 0 var(--card-border-inset-dark);
|
inset 0 -1px 0 var(--card-border-inset-dark);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mat-accordion .mat-expansion-panel::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
inset: -1px;
|
|
||||||
border-radius: inherit;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mat-expansion-panel-header {
|
.mat-expansion-panel-header {
|
||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
}
|
}
|
||||||
@@ -194,6 +118,7 @@ a {
|
|||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---- Material dialog overrides ---- */
|
||||||
.image-dialog-panel .mat-mdc-dialog-surface {
|
.image-dialog-panel .mat-mdc-dialog-surface {
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -210,251 +135,7 @@ a {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link-row {
|
/* ---- Material menu overrides ---- */
|
||||||
grid-row: 2;
|
|
||||||
grid-column: 2;
|
|
||||||
margin-top: .1rem;
|
|
||||||
opacity: .85;
|
|
||||||
vertical-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.link-with-icon {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: .35rem;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.link-with-icon mat-icon {
|
|
||||||
font-size: 18px;
|
|
||||||
height: 18px;
|
|
||||||
width: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
// algos
|
|
||||||
|
|
||||||
.algo-container {
|
|
||||||
max-width: var(--app-maxWidth);
|
|
||||||
gap: clamp(1rem, 3vw, 1.5rem);
|
|
||||||
margin-right: 1rem;
|
|
||||||
margin-left: 0.5rem;
|
|
||||||
margin-top: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.algo-info {
|
|
||||||
margin: 0 0 1rem 0;
|
|
||||||
padding: 0.75rem 1rem;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 8px;
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin: 0 0 0.5rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 0.5rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
margin-left: 0.25rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.controls-panel {
|
|
||||||
display: flex;
|
|
||||||
gap: 1rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
align-items: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
margin-top: 10px;
|
|
||||||
font-size: 0.9em;
|
|
||||||
|
|
||||||
mat-button-toggle-group {
|
|
||||||
border-radius: 4px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
mat-form-field {
|
|
||||||
width: 200px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-container {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.75rem;
|
|
||||||
align-items: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
|
|
||||||
.input-field {
|
|
||||||
width: 150px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas {
|
|
||||||
border: none;
|
|
||||||
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1);
|
|
||||||
display: block;
|
|
||||||
margin: 0 auto;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark canvas {
|
|
||||||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.legend {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 1rem;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 0.9em;
|
|
||||||
|
|
||||||
.legend-color {
|
|
||||||
display: inline-block;
|
|
||||||
width: 15px;
|
|
||||||
height: 15px;
|
|
||||||
border: 1px solid lightgray;
|
|
||||||
vertical-align: middle;
|
|
||||||
margin-right: 5px;
|
|
||||||
|
|
||||||
&.start {
|
|
||||||
background-color: green;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.end {
|
|
||||||
background-color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.wall {
|
|
||||||
background-color: black;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.visited {
|
|
||||||
background-color: skyblue;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.path {
|
|
||||||
background-color: gold;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.empty {
|
|
||||||
background-color: lightgray;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.alive {
|
|
||||||
background-color: black;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.L1 {
|
|
||||||
background-color: yellow;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.L2 {
|
|
||||||
background-color: magenta;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.M1 {
|
|
||||||
background-color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.M2 {
|
|
||||||
background-color: green;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.color1 {
|
|
||||||
background-color: #FF5252;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.color2 {
|
|
||||||
background-color: #448AFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.color3 {
|
|
||||||
background-color: #4CAF50;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.color4 {
|
|
||||||
background-color: #FFEB3B;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.controls-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Sorting Visualization & Canvas */
|
|
||||||
.sorting-visualization-area,
|
|
||||||
.visualization-area {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-end;
|
|
||||||
height: clamp(200px, 40vh, 400px);
|
|
||||||
border-bottom: 1px solid var(--app-fg);
|
|
||||||
margin-bottom: clamp(10px, 3vw, 20px);
|
|
||||||
gap: 1px;
|
|
||||||
background-color: var(--card-bg);
|
|
||||||
|
|
||||||
.sorting-bar,
|
|
||||||
.bar {
|
|
||||||
flex-grow: 1;
|
|
||||||
background-color: #424242;
|
|
||||||
transition: height 0.05s ease-in-out, background-color 0.05s ease-in-out;
|
|
||||||
width: 10px;
|
|
||||||
min-width: 1px;
|
|
||||||
|
|
||||||
&.unsorted {
|
|
||||||
background-color: #424242;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.comparing {
|
|
||||||
background-color: #ffeb3b;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.sorted {
|
|
||||||
background-color: #4caf50;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- Modern Layouts & Typography (Grid, Flex, Clamp) ---- */
|
|
||||||
|
|
||||||
.layout-container {
|
|
||||||
width: 100%;
|
|
||||||
max-width: var(--app-maxWidth);
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: clamp(1rem, 4vw, 2rem);
|
|
||||||
}
|
|
||||||
|
|
||||||
app-root {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-container {
|
|
||||||
width: 100%;
|
|
||||||
max-width: var(--app-maxWidth);
|
|
||||||
margin: 1rem auto;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-surface {
|
|
||||||
flex-grow: 1;
|
|
||||||
color: var(--app-fg);
|
|
||||||
transition: background-color 220ms ease, color 220ms ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.foot {
|
|
||||||
border-top: 1px solid rgba(0, 0, 0, .08);
|
|
||||||
padding: clamp(1rem, 2vw, 1.5rem);
|
|
||||||
text-align: center;
|
|
||||||
opacity: .8;
|
|
||||||
background: var(--app-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- Menu Overrides ---- */
|
|
||||||
.mat-mdc-menu-item .mdc-list-item__primary-text {
|
.mat-mdc-menu-item .mdc-list-item__primary-text {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -493,358 +174,7 @@ app-root {
|
|||||||
border-color: rgba(255, 255, 255, .06);
|
border-color: rgba(255, 255, 255, .06);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- About Page Sections ---- */
|
/* ---- Swiper shadow DOM (::part cannot be Tailwind) ---- */
|
||||||
.about,
|
|
||||||
.imprint {
|
|
||||||
display: grid;
|
|
||||||
gap: clamp(1rem, 3vw, 1.5rem);
|
|
||||||
max-width: var(--app-maxWidth);
|
|
||||||
margin-right: 1rem;
|
|
||||||
margin-left: 1rem;
|
|
||||||
margin-top: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero {
|
|
||||||
border-radius: var(--card-radius);
|
|
||||||
background: var(--card-bg);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-flex-container {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: clamp(1rem, 4vw, 2rem);
|
|
||||||
padding: clamp(1rem, 3vw, 1.5rem);
|
|
||||||
align-items: flex-start;
|
|
||||||
|
|
||||||
.photo {
|
|
||||||
flex: 1 1 min(100%, 425px);
|
|
||||||
max-width: 100%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
img {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
max-width: 425px;
|
|
||||||
border-radius: 12px;
|
|
||||||
box-shadow: 0 6px 24px rgba(0, 0, 0, .25);
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.intro {
|
|
||||||
flex: 999 1 min(100%, 400px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero .intro h1 {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
font-size: clamp(1.5rem, 5vw, 2.5rem);
|
|
||||||
background: linear-gradient(135deg, var(--mat-sys-primary), var(--mat-sys-tertiary));
|
|
||||||
background-clip: text;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero .intro .lead {
|
|
||||||
opacity: .9;
|
|
||||||
margin: 0.5rem 0 1rem;
|
|
||||||
font-size: clamp(1rem, 2.5vw, 1.15rem);
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero .intro .meta {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: .25rem;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero .intro .meta .row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: .4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero .intro .actions {
|
|
||||||
display: flex;
|
|
||||||
gap: .5rem;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
margin-top: .5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.skills,
|
|
||||||
.experience,
|
|
||||||
.projects,
|
|
||||||
.education {
|
|
||||||
padding: clamp(5px, 2vw, 15px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.skills h2,
|
|
||||||
.experience h2,
|
|
||||||
.projects h2,
|
|
||||||
.education h2 {
|
|
||||||
margin-top: .25rem;
|
|
||||||
margin-left: .25rem;
|
|
||||||
font-size: clamp(1.2rem, 4vw, 1.8rem);
|
|
||||||
}
|
|
||||||
|
|
||||||
.skills .chip-groups {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(min(100%, 250px), 1fr));
|
|
||||||
gap: clamp(0.5rem, 2vw, 1rem);
|
|
||||||
margin-left: .25rem;
|
|
||||||
margin-bottom: .5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.skills .chip-groups h3 {
|
|
||||||
margin: .2rem 0 .4rem;
|
|
||||||
font-size: .95rem;
|
|
||||||
opacity: .85;
|
|
||||||
}
|
|
||||||
|
|
||||||
.xp-list {
|
|
||||||
margin-left: .25rem;
|
|
||||||
display: grid;
|
|
||||||
gap: clamp(0.75rem, 2vw, 1rem);
|
|
||||||
}
|
|
||||||
|
|
||||||
.xp-item .xp-head {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: baseline;
|
|
||||||
gap: .5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.xp-item .xp-head .time {
|
|
||||||
opacity: .75;
|
|
||||||
font-size: clamp(0.85rem, 2vw, 0.95rem);
|
|
||||||
}
|
|
||||||
|
|
||||||
.xp-item .xp-sub {
|
|
||||||
opacity: .9;
|
|
||||||
margin-bottom: .25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.xp-item ul {
|
|
||||||
margin: .25rem 0 .5rem 1.15rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.xp-head-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: calc(clamp(32px, 8vw, 48px) + .75rem) 1fr;
|
|
||||||
grid-template-rows: auto auto;
|
|
||||||
column-gap: clamp(0.5rem, 2vw, .75rem);
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo-wrap {
|
|
||||||
grid-row: 1 / span 2;
|
|
||||||
grid-column: 1;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.company-logo {
|
|
||||||
width: clamp(32px, 8vw, 48px);
|
|
||||||
height: clamp(32px, 8vw, 48px);
|
|
||||||
object-fit: contain;
|
|
||||||
opacity: .9;
|
|
||||||
border-radius: 10%;
|
|
||||||
background-color: var(--app-logo-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.head-row {
|
|
||||||
grid-row: 1;
|
|
||||||
grid-column: 2;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: baseline;
|
|
||||||
gap: clamp(0.25rem, 1vw, 0.5rem) 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.head-row strong {
|
|
||||||
font-size: clamp(0.95rem, 2.5vw, 1.1rem);
|
|
||||||
}
|
|
||||||
|
|
||||||
.head-row .time {
|
|
||||||
opacity: .75;
|
|
||||||
font-size: clamp(0.85rem, 2vw, 0.95rem);
|
|
||||||
}
|
|
||||||
|
|
||||||
.company-row {
|
|
||||||
grid-row: 2;
|
|
||||||
grid-column: 2;
|
|
||||||
margin-top: .1rem;
|
|
||||||
opacity: .85;
|
|
||||||
font-size: clamp(0.85rem, 2vw, 1rem);
|
|
||||||
}
|
|
||||||
|
|
||||||
.highlights {
|
|
||||||
margin-top: .4rem;
|
|
||||||
margin-left: clamp(0.25rem, 1vw, .75rem);
|
|
||||||
padding-left: clamp(0.8rem, 2vw, 1.2rem);
|
|
||||||
}
|
|
||||||
|
|
||||||
.highlights li,
|
|
||||||
.highlights-noMargin li {
|
|
||||||
margin: .2rem 0;
|
|
||||||
font-size: clamp(0.9rem, 2vw, 1rem);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- Imprint ---- */
|
|
||||||
.imprint-card {
|
|
||||||
padding: clamp(1rem, 3vw, 1.5rem);
|
|
||||||
}
|
|
||||||
|
|
||||||
.imprint-title {
|
|
||||||
margin: 0 0 1rem;
|
|
||||||
font-size: clamp(1rem, 3vw, 1.2rem);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.imprint-section {
|
|
||||||
display: grid;
|
|
||||||
gap: 0.25rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.imprint-label {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
letter-spacing: 0.04em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
opacity: 0.7;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- Projects Page & Dialog ---- */
|
|
||||||
.card-grid {
|
|
||||||
display: grid;
|
|
||||||
gap: clamp(1rem, 3vw, 1.5rem);
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(min(100%, 450px), 1fr));
|
|
||||||
max-width: var(--app-maxWidth);
|
|
||||||
margin-right: 1rem;
|
|
||||||
margin-left: 1rem;
|
|
||||||
margin-top: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.algo-card {
|
|
||||||
transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
transform: translateY(-4px);
|
|
||||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.algo-card::after, .project-card::after {
|
|
||||||
inset: unset;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
height: 3px;
|
|
||||||
background: linear-gradient(90deg, var(--mat-sys-primary), var(--mat-sys-tertiary));
|
|
||||||
border-radius: var(--card-radius) var(--card-radius) 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.algo-icon-wrap {
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
border-radius: 12px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background: color-mix(in oklab, var(--mat-sys-primary) 15%, transparent);
|
|
||||||
color: var(--mat-sys-primary);
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
|
|
||||||
mat-icon {
|
|
||||||
font-size: 26px;
|
|
||||||
width: 26px;
|
|
||||||
height: 26px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.algo-page-title {
|
|
||||||
margin: 0 0 0.5rem 0;
|
|
||||||
font-size: clamp(1.4rem, 4vw, 2rem);
|
|
||||||
}
|
|
||||||
|
|
||||||
.algo-card-title {
|
|
||||||
font-size: 1.05rem;
|
|
||||||
font-weight: 600;
|
|
||||||
margin: 0 0 0.5rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.algo-card-desc {
|
|
||||||
margin: 0;
|
|
||||||
opacity: 0.75;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
line-height: 1.55;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-card {
|
|
||||||
transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-card:hover {
|
|
||||||
transform: translateY(-5px);
|
|
||||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-card.featured {
|
|
||||||
grid-column: 1 / -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-card mat-card-header {
|
|
||||||
padding-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-card mat-card-content {
|
|
||||||
flex-grow: 1;
|
|
||||||
padding-top: 1rem;
|
|
||||||
padding-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-card mat-chip-set {
|
|
||||||
padding-top: clamp(0.5rem, 2vw, 1rem);
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-card mat-card-actions {
|
|
||||||
margin-top: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
height: clamp(150px, 20vw, 200px);
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fallback-icon {
|
|
||||||
font-size: clamp(3rem, 8vw, 4rem);
|
|
||||||
width: clamp(3rem, 8vw, 4rem);
|
|
||||||
height: clamp(3rem, 8vw, 4rem);
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
img[mat-card-image] {
|
|
||||||
width: 100%;
|
|
||||||
height: clamp(150px, 25vw, 250px);
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.my-swiper {
|
|
||||||
border-radius: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.my-swiper::part(button-prev),
|
.my-swiper::part(button-prev),
|
||||||
.my-swiper::part(button-next) {
|
.my-swiper::part(button-next) {
|
||||||
width: 35px;
|
width: 35px;
|
||||||
@@ -866,101 +196,3 @@ img[mat-card-image] {
|
|||||||
.my-swiper::part(pagination) {
|
.my-swiper::part(pagination) {
|
||||||
bottom: 12px;
|
bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
swiper-slide {
|
|
||||||
border-radius: 12px;
|
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
background-color: #222;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-img {
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
max-height: clamp(300px, 60vh, 512px) !important;
|
|
||||||
object-fit: contain;
|
|
||||||
display: block;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-source {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
color: #aaa;
|
|
||||||
background: #2a2a2a;
|
|
||||||
padding: 0.5rem;
|
|
||||||
text-align: right;
|
|
||||||
border-top: 1px solid #444;
|
|
||||||
}
|
|
||||||
|
|
||||||
.link-section {
|
|
||||||
display: flex;
|
|
||||||
gap: clamp(0.5rem, 2vw, 1rem);
|
|
||||||
margin-top: 1.5rem;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- Shared Elements ---- */
|
|
||||||
.canvas-container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 1000px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.canvas-container canvas {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
aspect-ratio: 1 / 1;
|
|
||||||
min-width: 200px;
|
|
||||||
max-width: 1000px;
|
|
||||||
touch-action: none;
|
|
||||||
border: none;
|
|
||||||
border-radius: clamp(10px, 2vw, 20px);
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.category-cards {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: clamp(0.5rem, 2vw, 1rem);
|
|
||||||
margin-top: clamp(1rem, 3vw, 2rem);
|
|
||||||
}
|
|
||||||
|
|
||||||
.category-cards mat-card {
|
|
||||||
cursor: pointer;
|
|
||||||
flex: 1 1 300px;
|
|
||||||
min-width: 300px;
|
|
||||||
max-width: 450px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.category-cards mat-card:hover {
|
|
||||||
transform: translateY(-5px);
|
|
||||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sorting-card {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 1920px;
|
|
||||||
padding: clamp(10px, 3vw, 20px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sorting-card .controls-panel {
|
|
||||||
display: flex;
|
|
||||||
gap: clamp(5px, 2vw, 10px);
|
|
||||||
margin-bottom: clamp(10px, 3vw, 20px);
|
|
||||||
align-items: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sorting-card .controls-panel mat-form-field {
|
|
||||||
width: clamp(150px, 20vw, 200px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sorting-card .info-panel {
|
|
||||||
margin-top: 10px;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
|
|||||||
67
src/tailwind.css
Normal file
67
src/tailwind.css
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
*,
|
||||||
|
::before,
|
||||||
|
::after {
|
||||||
|
border-width: 0;
|
||||||
|
border-style: solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: Inter, Roboto, Arial, sans-serif;
|
||||||
|
background-color: var(--app-bg);
|
||||||
|
color: var(--app-fg);
|
||||||
|
transition: background-color 220ms ease, color 220ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
@apply text-link no-underline font-medium hover:text-link-hover hover:underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.material-symbols-outlined {
|
||||||
|
font-variation-settings: "FILL" 0, "wght" 400, "GRAD" 0, "opsz" 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark body {
|
||||||
|
background: radial-gradient(ellipse at 50% 0%, #1e2530 0%, #1a1a1a 65%);
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas {
|
||||||
|
@apply border-none block mx-auto max-w-full;
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark canvas {
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer components {
|
||||||
|
.legend-swatch {
|
||||||
|
@apply inline-block w-[15px] h-[15px] border border-gray-300 align-middle mr-[5px];
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-gradient-bar {
|
||||||
|
@apply relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-gradient-bar::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 3px;
|
||||||
|
background: linear-gradient(90deg, var(--mat-sys-primary), var(--mat-sys-tertiary));
|
||||||
|
border-radius: var(--card-radius) var(--card-radius) 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
37
tailwind.config.js
Normal file
37
tailwind.config.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
module.exports = {
|
||||||
|
content: ['./src/**/*.{html,ts}'],
|
||||||
|
darkMode: 'class',
|
||||||
|
corePlugins: { preflight: false },
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
'app-bg': 'var(--app-bg)',
|
||||||
|
'app-fg': 'var(--app-fg)',
|
||||||
|
'card-bg': 'var(--card-bg)',
|
||||||
|
'link': 'var(--link-color)',
|
||||||
|
'link-hover': 'var(--link-color-hover)',
|
||||||
|
'logo-bg': 'var(--app-logo-bg)',
|
||||||
|
},
|
||||||
|
maxWidth: {
|
||||||
|
'app': 'var(--app-maxWidth)',
|
||||||
|
},
|
||||||
|
borderRadius: {
|
||||||
|
'card': 'var(--card-radius)',
|
||||||
|
},
|
||||||
|
fontFamily: {
|
||||||
|
'sans': ['Inter', 'Roboto', 'Arial', 'sans-serif'],
|
||||||
|
},
|
||||||
|
spacing: {
|
||||||
|
'fluid-sm': 'clamp(0.5rem, 2vw, 1rem)',
|
||||||
|
'fluid-md': 'clamp(1rem, 3vw, 1.5rem)',
|
||||||
|
'fluid-lg': 'clamp(1rem, 4vw, 2rem)',
|
||||||
|
},
|
||||||
|
screens: {
|
||||||
|
'mobile': { 'max': '760px' },
|
||||||
|
'sm-dialog': { 'max': '600px' },
|
||||||
|
'tablet': { 'max': '900px' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user