Topbar active links, algorithm icons & styles
Add active state support to topbar links (routerLinkActive) and CSS underline/hover styling; expose icons for algorithm categories and render them in the algorithms list. Update AlgorithmCategory interface and AlgorithmsService to include icon names, import MatIconModule where needed, and adjust algorithms template to show icon, title and description layout. Global style tweaks: dark theme background, canvas shadows, card hover/gradient accents, and new styles for algorithm cards and page title for improved visual polish.
This commit is contained in:
@@ -6,10 +6,10 @@
|
||||
</a>
|
||||
|
||||
<nav class="nav">
|
||||
<a [routerLink]="RouterConstants.ABOUT.LINK" mat-button>{{ 'TOPBAR.ABOUT' | translate }}</a>
|
||||
<a [routerLink]="RouterConstants.PROJECTS.LINK" mat-button>{{ 'TOPBAR.PROJECTS' | translate }}</a>
|
||||
<a [routerLink]="RouterConstants.ALGORITHMS.LINK" mat-button>{{ 'TOPBAR.ALGORITHMS' | translate }}</a>
|
||||
<a [routerLink]="RouterConstants.IMPRINT.LINK" mat-button>{{ 'TOPBAR.IMPRINT' | translate }}</a>
|
||||
<a [routerLink]="RouterConstants.ABOUT.LINK" routerLinkActive="active" mat-button>{{ 'TOPBAR.ABOUT' | translate }}</a>
|
||||
<a [routerLink]="RouterConstants.PROJECTS.LINK" routerLinkActive="active" mat-button>{{ 'TOPBAR.PROJECTS' | translate }}</a>
|
||||
<a [routerLink]="RouterConstants.ALGORITHMS.LINK" routerLinkActive="active" mat-button>{{ 'TOPBAR.ALGORITHMS' | translate }}</a>
|
||||
<a [routerLink]="RouterConstants.IMPRINT.LINK" routerLinkActive="active" mat-button>{{ 'TOPBAR.IMPRINT' | translate }}</a>
|
||||
</nav>
|
||||
|
||||
<!-- Mobile nav menu button -->
|
||||
|
||||
@@ -52,6 +52,37 @@
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Component, computed, inject } from '@angular/core';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { RouterLink, RouterLinkActive } from '@angular/router';
|
||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
@@ -15,7 +15,7 @@ import {RouterConstants} from '../../constants/RouterConstants';
|
||||
@Component({
|
||||
selector: 'app-topbar',
|
||||
imports: [
|
||||
RouterLink,
|
||||
RouterLink, RouterLinkActive,
|
||||
MatToolbarModule, MatIconModule, MatButtonModule, MatMenuModule, MatTooltipModule,
|
||||
TranslateModule, MatDivider
|
||||
],
|
||||
|
||||
@@ -3,4 +3,5 @@ export interface AlgorithmCategory {
|
||||
title: string;
|
||||
description: string;
|
||||
routerLink: string;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
<div class="card-grid">
|
||||
<h1>{{ 'ALGORITHM.TITLE' |translate }}</h1>
|
||||
<h1 class="algo-page-title">{{ 'ALGORITHM.TITLE' | translate }}</h1>
|
||||
</div>
|
||||
<div class="card-grid">
|
||||
@for (category of categories; track category.id) {
|
||||
<mat-card class="algo-card" [routerLink]="[category.routerLink]">
|
||||
<mat-card-header>
|
||||
<mat-card-title>{{ category.title | translate }}</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<p>{{ category.description | translate}}</p>
|
||||
<div class="algo-icon-wrap">
|
||||
<mat-icon>{{ category.icon }}</mat-icon>
|
||||
</div>
|
||||
<h3 class="algo-card-title">{{ category.title | translate }}</h3>
|
||||
<p class="algo-card-desc">{{ category.description | translate }}</p>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
}
|
||||
|
||||
@@ -3,13 +3,14 @@ import { AlgorithmsService } from './algorithms.service';
|
||||
import { AlgorithmCategory } from './algorithm-category';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import {TranslatePipe} from '@ngx-translate/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-algorithms',
|
||||
templateUrl: './algorithms.component.html',
|
||||
styleUrl: './algorithms.component.scss',
|
||||
imports: [RouterLink, MatCardModule, TranslatePipe],
|
||||
imports: [RouterLink, MatCardModule, MatIconModule, TranslatePipe],
|
||||
})
|
||||
export class AlgorithmsComponent {
|
||||
private readonly algorithmsService = inject(AlgorithmsService);
|
||||
|
||||
@@ -12,49 +12,57 @@ export class AlgorithmsService {
|
||||
id: 'pathfinding',
|
||||
title: 'ALGORITHM.PATHFINDING.TITLE',
|
||||
description: 'ALGORITHM.PATHFINDING.DESCRIPTION',
|
||||
routerLink: RouterConstants.PATHFINDING.LINK
|
||||
routerLink: RouterConstants.PATHFINDING.LINK,
|
||||
icon: 'route'
|
||||
},
|
||||
{
|
||||
id: 'sorting',
|
||||
title: 'ALGORITHM.SORTING.TITLE',
|
||||
description: 'ALGORITHM.SORTING.DESCRIPTION',
|
||||
routerLink: RouterConstants.SORTING.LINK
|
||||
routerLink: RouterConstants.SORTING.LINK,
|
||||
icon: 'sort'
|
||||
},
|
||||
{
|
||||
id: 'gameOfLife',
|
||||
title: 'ALGORITHM.GOL.TITLE',
|
||||
description: 'ALGORITHM.GOL.DESCRIPTION',
|
||||
routerLink: RouterConstants.GOL.LINK
|
||||
routerLink: RouterConstants.GOL.LINK,
|
||||
icon: 'grid_on'
|
||||
},
|
||||
{
|
||||
id: 'labyrinth',
|
||||
title: 'ALGORITHM.LABYRINTH.TITLE',
|
||||
description: 'ALGORITHM.LABYRINTH.DESCRIPTION',
|
||||
routerLink: RouterConstants.LABYRINTH.LINK
|
||||
routerLink: RouterConstants.LABYRINTH.LINK,
|
||||
icon: 'grid_view'
|
||||
},
|
||||
{
|
||||
id: 'fractal',
|
||||
title: 'ALGORITHM.FRACTAL.TITLE',
|
||||
description: 'ALGORITHM.FRACTAL.DESCRIPTION',
|
||||
routerLink: RouterConstants.FRACTAL.LINK
|
||||
routerLink: RouterConstants.FRACTAL.LINK,
|
||||
icon: 'blur_on'
|
||||
},
|
||||
{
|
||||
id: 'fractal3d',
|
||||
title: 'ALGORITHM.FRACTAL3D.TITLE',
|
||||
description: 'ALGORITHM.FRACTAL3D.DESCRIPTION',
|
||||
routerLink: RouterConstants.FRACTAL3d.LINK
|
||||
routerLink: RouterConstants.FRACTAL3d.LINK,
|
||||
icon: 'view_in_ar'
|
||||
},
|
||||
{
|
||||
id: 'pendulum',
|
||||
title: 'ALGORITHM.PENDULUM.TITLE',
|
||||
description: 'ALGORITHM.PENDULUM.DESCRIPTION',
|
||||
routerLink: RouterConstants.PENDULUM.LINK
|
||||
routerLink: RouterConstants.PENDULUM.LINK,
|
||||
icon: 'rotate_right'
|
||||
},
|
||||
{
|
||||
id: 'cloth',
|
||||
title: 'ALGORITHM.CLOTH.TITLE',
|
||||
description: 'ALGORITHM.CLOTH.DESCRIPTION',
|
||||
routerLink: RouterConstants.CLOTH.LINK
|
||||
routerLink: RouterConstants.CLOTH.LINK,
|
||||
icon: 'texture'
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -50,6 +50,10 @@ $dark-theme: mat.define-theme((color: (theme-type: dark, primary: mat.$cyan-pale
|
||||
--link-color-hover: #9ad2ff;
|
||||
}
|
||||
|
||||
.dark body {
|
||||
background: radial-gradient(ellipse at 50% 0%, #1e2530 0%, #1a1a1a 65%);
|
||||
}
|
||||
|
||||
/* ---- global background and tests ---- */
|
||||
html,
|
||||
body {
|
||||
@@ -287,12 +291,17 @@ a {
|
||||
}
|
||||
|
||||
canvas {
|
||||
border: 1px solid lightgray;
|
||||
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;
|
||||
@@ -518,6 +527,10 @@ app-root {
|
||||
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));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.hero .intro .lead {
|
||||
@@ -706,6 +719,57 @@ app-root {
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user