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>
|
</a>
|
||||||
|
|
||||||
<nav class="nav">
|
<nav class="nav">
|
||||||
<a [routerLink]="RouterConstants.ABOUT.LINK" mat-button>{{ 'TOPBAR.ABOUT' | translate }}</a>
|
<a [routerLink]="RouterConstants.ABOUT.LINK" routerLinkActive="active" mat-button>{{ 'TOPBAR.ABOUT' | translate }}</a>
|
||||||
<a [routerLink]="RouterConstants.PROJECTS.LINK" mat-button>{{ 'TOPBAR.PROJECTS' | translate }}</a>
|
<a [routerLink]="RouterConstants.PROJECTS.LINK" routerLinkActive="active" mat-button>{{ 'TOPBAR.PROJECTS' | translate }}</a>
|
||||||
<a [routerLink]="RouterConstants.ALGORITHMS.LINK" mat-button>{{ 'TOPBAR.ALGORITHMS' | translate }}</a>
|
<a [routerLink]="RouterConstants.ALGORITHMS.LINK" routerLinkActive="active" mat-button>{{ 'TOPBAR.ALGORITHMS' | translate }}</a>
|
||||||
<a [routerLink]="RouterConstants.IMPRINT.LINK" mat-button>{{ 'TOPBAR.IMPRINT' | translate }}</a>
|
<a [routerLink]="RouterConstants.IMPRINT.LINK" routerLinkActive="active" mat-button>{{ 'TOPBAR.IMPRINT' | translate }}</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<!-- Mobile nav menu button -->
|
<!-- Mobile nav menu button -->
|
||||||
|
|||||||
@@ -52,6 +52,37 @@
|
|||||||
justify-content: center;
|
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 {
|
.nav-menu-btn {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Component, computed, inject } from '@angular/core';
|
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 { MatToolbarModule } from '@angular/material/toolbar';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
@@ -15,7 +15,7 @@ import {RouterConstants} from '../../constants/RouterConstants';
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'app-topbar',
|
selector: 'app-topbar',
|
||||||
imports: [
|
imports: [
|
||||||
RouterLink,
|
RouterLink, RouterLinkActive,
|
||||||
MatToolbarModule, MatIconModule, MatButtonModule, MatMenuModule, MatTooltipModule,
|
MatToolbarModule, MatIconModule, MatButtonModule, MatMenuModule, MatTooltipModule,
|
||||||
TranslateModule, MatDivider
|
TranslateModule, MatDivider
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -3,4 +3,5 @@ export interface AlgorithmCategory {
|
|||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
routerLink: string;
|
routerLink: string;
|
||||||
|
icon: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
<div class="card-grid">
|
<div class="card-grid">
|
||||||
<h1>{{ 'ALGORITHM.TITLE' |translate }}</h1>
|
<h1 class="algo-page-title">{{ 'ALGORITHM.TITLE' | translate }}</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-grid">
|
<div class="card-grid">
|
||||||
@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="algo-card" [routerLink]="[category.routerLink]">
|
||||||
<mat-card-header>
|
|
||||||
<mat-card-title>{{ category.title | translate }}</mat-card-title>
|
|
||||||
</mat-card-header>
|
|
||||||
<mat-card-content>
|
<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-content>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,14 @@ import { AlgorithmsService } from './algorithms.service';
|
|||||||
import { AlgorithmCategory } from './algorithm-category';
|
import { AlgorithmCategory } from './algorithm-category';
|
||||||
import { RouterLink } from '@angular/router';
|
import { RouterLink } from '@angular/router';
|
||||||
import { MatCardModule } from '@angular/material/card';
|
import { MatCardModule } from '@angular/material/card';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import {TranslatePipe} from '@ngx-translate/core';
|
import {TranslatePipe} from '@ngx-translate/core';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-algorithms',
|
selector: 'app-algorithms',
|
||||||
templateUrl: './algorithms.component.html',
|
templateUrl: './algorithms.component.html',
|
||||||
styleUrl: './algorithms.component.scss',
|
styleUrl: './algorithms.component.scss',
|
||||||
imports: [RouterLink, MatCardModule, TranslatePipe],
|
imports: [RouterLink, MatCardModule, MatIconModule, TranslatePipe],
|
||||||
})
|
})
|
||||||
export class AlgorithmsComponent {
|
export class AlgorithmsComponent {
|
||||||
private readonly algorithmsService = inject(AlgorithmsService);
|
private readonly algorithmsService = inject(AlgorithmsService);
|
||||||
|
|||||||
@@ -12,49 +12,57 @@ export class AlgorithmsService {
|
|||||||
id: 'pathfinding',
|
id: 'pathfinding',
|
||||||
title: 'ALGORITHM.PATHFINDING.TITLE',
|
title: 'ALGORITHM.PATHFINDING.TITLE',
|
||||||
description: 'ALGORITHM.PATHFINDING.DESCRIPTION',
|
description: 'ALGORITHM.PATHFINDING.DESCRIPTION',
|
||||||
routerLink: RouterConstants.PATHFINDING.LINK
|
routerLink: RouterConstants.PATHFINDING.LINK,
|
||||||
|
icon: 'route'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'sorting',
|
id: 'sorting',
|
||||||
title: 'ALGORITHM.SORTING.TITLE',
|
title: 'ALGORITHM.SORTING.TITLE',
|
||||||
description: 'ALGORITHM.SORTING.DESCRIPTION',
|
description: 'ALGORITHM.SORTING.DESCRIPTION',
|
||||||
routerLink: RouterConstants.SORTING.LINK
|
routerLink: RouterConstants.SORTING.LINK,
|
||||||
|
icon: 'sort'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'gameOfLife',
|
id: 'gameOfLife',
|
||||||
title: 'ALGORITHM.GOL.TITLE',
|
title: 'ALGORITHM.GOL.TITLE',
|
||||||
description: 'ALGORITHM.GOL.DESCRIPTION',
|
description: 'ALGORITHM.GOL.DESCRIPTION',
|
||||||
routerLink: RouterConstants.GOL.LINK
|
routerLink: RouterConstants.GOL.LINK,
|
||||||
|
icon: 'grid_on'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'labyrinth',
|
id: 'labyrinth',
|
||||||
title: 'ALGORITHM.LABYRINTH.TITLE',
|
title: 'ALGORITHM.LABYRINTH.TITLE',
|
||||||
description: 'ALGORITHM.LABYRINTH.DESCRIPTION',
|
description: 'ALGORITHM.LABYRINTH.DESCRIPTION',
|
||||||
routerLink: RouterConstants.LABYRINTH.LINK
|
routerLink: RouterConstants.LABYRINTH.LINK,
|
||||||
|
icon: 'grid_view'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'fractal',
|
id: 'fractal',
|
||||||
title: 'ALGORITHM.FRACTAL.TITLE',
|
title: 'ALGORITHM.FRACTAL.TITLE',
|
||||||
description: 'ALGORITHM.FRACTAL.DESCRIPTION',
|
description: 'ALGORITHM.FRACTAL.DESCRIPTION',
|
||||||
routerLink: RouterConstants.FRACTAL.LINK
|
routerLink: RouterConstants.FRACTAL.LINK,
|
||||||
|
icon: 'blur_on'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'fractal3d',
|
id: 'fractal3d',
|
||||||
title: 'ALGORITHM.FRACTAL3D.TITLE',
|
title: 'ALGORITHM.FRACTAL3D.TITLE',
|
||||||
description: 'ALGORITHM.FRACTAL3D.DESCRIPTION',
|
description: 'ALGORITHM.FRACTAL3D.DESCRIPTION',
|
||||||
routerLink: RouterConstants.FRACTAL3d.LINK
|
routerLink: RouterConstants.FRACTAL3d.LINK,
|
||||||
|
icon: 'view_in_ar'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'pendulum',
|
id: 'pendulum',
|
||||||
title: 'ALGORITHM.PENDULUM.TITLE',
|
title: 'ALGORITHM.PENDULUM.TITLE',
|
||||||
description: 'ALGORITHM.PENDULUM.DESCRIPTION',
|
description: 'ALGORITHM.PENDULUM.DESCRIPTION',
|
||||||
routerLink: RouterConstants.PENDULUM.LINK
|
routerLink: RouterConstants.PENDULUM.LINK,
|
||||||
|
icon: 'rotate_right'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'cloth',
|
id: 'cloth',
|
||||||
title: 'ALGORITHM.CLOTH.TITLE',
|
title: 'ALGORITHM.CLOTH.TITLE',
|
||||||
description: 'ALGORITHM.CLOTH.DESCRIPTION',
|
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;
|
--link-color-hover: #9ad2ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark body {
|
||||||
|
background: radial-gradient(ellipse at 50% 0%, #1e2530 0%, #1a1a1a 65%);
|
||||||
|
}
|
||||||
|
|
||||||
/* ---- global background and tests ---- */
|
/* ---- global background and tests ---- */
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
@@ -287,12 +291,17 @@ a {
|
|||||||
}
|
}
|
||||||
|
|
||||||
canvas {
|
canvas {
|
||||||
border: 1px solid lightgray;
|
border: none;
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1);
|
||||||
display: block;
|
display: block;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark canvas {
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
.legend {
|
.legend {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -518,6 +527,10 @@ app-root {
|
|||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
font-size: clamp(1.5rem, 5vw, 2.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 {
|
.hero .intro .lead {
|
||||||
@@ -706,6 +719,57 @@ app-root {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
cursor: pointer;
|
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 {
|
.project-card {
|
||||||
|
|||||||
Reference in New Issue
Block a user