(false);
-}
diff --git a/src/app/ui/sections/cds-selected-section/cds-selected-section.html b/src/app/ui/sections/cds-selected-section/cds-selected-section.html
index f0ca06c3..14284ce4 100644
--- a/src/app/ui/sections/cds-selected-section/cds-selected-section.html
+++ b/src/app/ui/sections/cds-selected-section/cds-selected-section.html
@@ -8,7 +8,7 @@
@if (infoCds.status() === ERR_STATUS) {
-
+
Qualcosa è andato storto :/
}
diff --git a/src/app/ui/sections/cds-selected-section/cds-selected-section.ts b/src/app/ui/sections/cds-selected-section/cds-selected-section.ts
index 1cef420f..dc59354e 100644
--- a/src/app/ui/sections/cds-selected-section/cds-selected-section.ts
+++ b/src/app/ui/sections/cds-selected-section/cds-selected-section.ts
@@ -5,14 +5,14 @@ import {
inject,
ResourceStatus,
} from '@angular/core';
-import { Graph } from '@components/graph/graph';
-import { Icon } from '@components/icon/icon';
-import { Loader } from '@components/loader/loader';
import { CdsService } from '@services/cds/cds.service';
+import { Graph } from '@shared-ui/graph/graph';
+import { IconComponent } from '@shared-ui/icon/icon';
+import { Loader } from '@shared-ui/loader/loader';
@Component({
selector: 'opis-cds-selected-section',
- imports: [Icon, Loader, Graph],
+ imports: [IconComponent, Loader, Graph],
templateUrl: './cds-selected-section.html',
styleUrl: './cds-selected-section.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
diff --git a/src/app/ui/sections/footer/footer.html b/src/app/ui/sections/footer/footer.html
new file mode 100644
index 00000000..a8fb93d6
--- /dev/null
+++ b/src/app/ui/sections/footer/footer.html
@@ -0,0 +1,28 @@
+
diff --git a/src/app/ui/sections/footer/footer.scss b/src/app/ui/sections/footer/footer.scss
new file mode 100644
index 00000000..e2403ddc
--- /dev/null
+++ b/src/app/ui/sections/footer/footer.scss
@@ -0,0 +1,35 @@
+.opis-footer {
+ width: 100%;
+ margin: 20px 0px;
+ background-color: var(--dark-bg);
+ color: var(--gray-400);
+ display: flex;
+ flex-flow: row wrap;
+ align-items: center;
+ justify-content: center;
+
+ .opis-license {
+ display: inline-flex;
+ align-items: center;
+ justify-content: start;
+ gap: 0.2rem;
+
+ small {
+ text-decoration: underline;
+ margin-bottom: 0.4rem;
+ display: block;
+ }
+ }
+
+ .opis-quote {
+ display: flex;
+ justify-content: center;
+ gap: 1.2rem;
+ text-align: center;
+ }
+
+ @media (min-width: 800px) {
+ justify-content: space-between;
+ padding: 0.5rem 2rem;
+ }
+}
diff --git a/src/app/ui/sections/footer/footer.ts b/src/app/ui/sections/footer/footer.ts
new file mode 100644
index 00000000..8d87404a
--- /dev/null
+++ b/src/app/ui/sections/footer/footer.ts
@@ -0,0 +1,22 @@
+import { ChangeDetectionStrategy, Component, inject, OnInit, signal } from '@angular/core';
+import { IconRegistryService } from '@services/icon-registry/icon-registry.service';
+import { IconComponent } from '@shared-ui/icon/icon';
+
+@Component({
+ selector: 'opis-footer',
+ imports: [IconComponent],
+ templateUrl: './footer.html',
+ styleUrl: './footer.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class Footer implements OnInit {
+ private readonly _iconRegistryService = inject(IconRegistryService);
+
+ protected readonly githubIcon = signal('');
+
+ ngOnInit(): void {
+ this._iconRegistryService.load('github').subscribe((icon) => {
+ this.githubIcon.set(icon);
+ });
+ }
+}
diff --git a/src/app/ui/sections/header-nav/header-nav.html b/src/app/ui/sections/header-nav/header-nav.html
new file mode 100644
index 00000000..cde8d807
--- /dev/null
+++ b/src/app/ui/sections/header-nav/header-nav.html
@@ -0,0 +1,20 @@
+
diff --git a/src/app/ui/sections/header-nav/header-nav.scss b/src/app/ui/sections/header-nav/header-nav.scss
new file mode 100644
index 00000000..2a19ed15
--- /dev/null
+++ b/src/app/ui/sections/header-nav/header-nav.scss
@@ -0,0 +1,47 @@
+.opis-header {
+ background-color: var(--dark-bg);
+
+ .header-content {
+ margin: 0 auto;
+ width: 100%;
+ padding: 0.5rem 1rem;
+ display: inline-flex;
+ gap: 1rem;
+ align-items: end;
+ justify-content: space-between;
+ color: var(--color-surface);
+ font-size: 1.2rem;
+
+ .opis-logo {
+ display: inline-flex;
+ gap: 1rem;
+ align-items: end;
+ width: 3rem;
+ height: 3rem;
+
+ img {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+ }
+
+ h1 {
+ white-space: nowrap;
+ font-weight: 400;
+ font-size: 2rem;
+ display: none;
+
+ @media (min-width: 600px) {
+ display: block;
+ }
+ }
+ }
+
+ .end-nav-items {
+ display: inline-flex;
+ align-items: center;
+ gap: 1rem;
+ padding-bottom: 0.3rem;
+ }
+ }
+}
diff --git a/src/app/ui/sections/header-nav/header-nav.ts b/src/app/ui/sections/header-nav/header-nav.ts
new file mode 100644
index 00000000..48251baa
--- /dev/null
+++ b/src/app/ui/sections/header-nav/header-nav.ts
@@ -0,0 +1,26 @@
+import { ChangeDetectionStrategy, Component } from '@angular/core';
+import { RouterLink } from '@angular/router';
+import { NavItem } from '@interfaces/header-nav-interface';
+import { IconComponent } from '@shared-ui/icon/icon';
+
+@Component({
+ selector: 'opis-header-nav',
+ imports: [RouterLink, IconComponent],
+ templateUrl: './header-nav.html',
+ styleUrl: './header-nav.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class HeaderNav {
+ protected readonly NavItems: NavItem[] = [
+ { label: 'Formula', route: '/formula' },
+ { label: 'Info', route: '/info' },
+ {
+ label: 'Login',
+ route: '/login',
+ icon: {
+ name: 'account_circle',
+ dimension: '2rem',
+ },
+ },
+ ];
+}
diff --git a/src/app/ui/shared/graph/grap.html b/src/app/ui/shared/graph/grap.html
new file mode 100644
index 00000000..c82ff63b
--- /dev/null
+++ b/src/app/ui/shared/graph/grap.html
@@ -0,0 +1,10 @@
+@if (dataChart()) {
+
+}
diff --git a/src/app/ui/shared/graph/graph.scss b/src/app/ui/shared/graph/graph.scss
new file mode 100644
index 00000000..3651c161
--- /dev/null
+++ b/src/app/ui/shared/graph/graph.scss
@@ -0,0 +1,11 @@
+#graph {
+ width: 93%;
+ margin: 1rem auto;
+ display: flex;
+ justify-content: center;
+
+ @media (min-width: 1300px) {
+ width: 60%;
+ margin: 1.5rem auto;
+ }
+}
diff --git a/src/app/ui/shared/graph/graph.ts b/src/app/ui/shared/graph/graph.ts
new file mode 100644
index 00000000..d2fb2c38
--- /dev/null
+++ b/src/app/ui/shared/graph/graph.ts
@@ -0,0 +1,88 @@
+import { ChangeDetectionStrategy, Component, input, OnInit } from '@angular/core';
+import { OpisGroup, OpisGroupType } from '@enums/opis-group.enum';
+import { GraphView } from '@interfaces/graph-config.interface';
+import { ChartConfiguration, ChartData } from 'chart.js';
+import { BaseChartDirective } from 'ng2-charts';
+
+@Component({
+ selector: 'opis-graph',
+ imports: [BaseChartDirective],
+ templateUrl: `./grap.html`,
+ styleUrl: './graph.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class Graph implements OnInit {
+ protected readonly chartOptions = this.setInitOptions();
+
+ readonly dataChart = input.required();
+ protected coloredDataChart: ChartData;
+
+ ngOnInit(): void {
+ this.addBrandColorToDataset();
+ }
+
+ private isOpisGroup(value: unknown): value is OpisGroupType {
+ return Object.values(OpisGroup).includes(value as OpisGroupType);
+ }
+
+ private cssVar(name: string): string {
+ return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
+ }
+
+ private hexToRgba(hex: string, alpha: number): string {
+ const r = parseInt(hex.slice(1, 3), 16);
+ const g = parseInt(hex.slice(3, 5), 16);
+ const b = parseInt(hex.slice(5, 7), 16);
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
+ }
+
+ private getColor(labelGroup: OpisGroupType): string {
+ const blue = this.cssVar('--blue-900');
+ const red = this.cssVar('--red-900');
+ const sand = this.cssVar('--sand-500');
+ const defaultGrey = this.cssVar('--gray-700');
+
+ const colors = new Map([
+ [OpisGroup.Group1, blue],
+ [OpisGroup.Group2, red],
+ [OpisGroup.Group3, sand],
+ ]);
+
+ return colors.get(labelGroup) ?? defaultGrey;
+ }
+
+ private addBrandColorToDataset(): void {
+ const datasets = this.dataChart().data.datasets;
+
+ this.dataChart().data.datasets = datasets.map((set) => {
+ const label = set.label;
+
+ if (!this.isOpisGroup(label)) {
+ return set;
+ }
+
+ const color = this.getColor(label);
+
+ return {
+ ...set,
+ borderColor: color,
+ backgroundColor: this.hexToRgba(color, 0.6),
+ };
+ });
+ }
+
+ private setInitOptions(): ChartConfiguration['options'] {
+ return {
+ // We use these empty structures as placeholders for dynamic theming.
+ scales: {
+ x: {},
+ y: {},
+ },
+ plugins: {
+ legend: {
+ display: true,
+ },
+ },
+ };
+ }
+}
diff --git a/src/app/ui/shared/icon/icon.component.html b/src/app/ui/shared/icon/icon.component.html
new file mode 100644
index 00000000..c5422a35
--- /dev/null
+++ b/src/app/ui/shared/icon/icon.component.html
@@ -0,0 +1,7 @@
+@if (safeSvgIcon()) {
+
+} @else {
+
+ {{ iconName() }}
+
+}
diff --git a/src/app/ui/shared/icon/icon.component.scss b/src/app/ui/shared/icon/icon.component.scss
new file mode 100644
index 00000000..ef374b7c
--- /dev/null
+++ b/src/app/ui/shared/icon/icon.component.scss
@@ -0,0 +1,44 @@
+.opis-icon {
+ display: inline-flex;
+ line-height: 1;
+
+ &.size-1-5rem {
+ font-size: 1.5rem;
+ }
+
+ &.size-2rem {
+ font-size: 2rem;
+ }
+
+ &.size-2-5rem {
+ font-size: 2.5rem;
+ }
+
+ &.size-3rem {
+ font-size: 3rem;
+ }
+
+ &.size-3-5rem {
+ font-size: 3.5rem;
+ }
+
+ &.size-4rem {
+ font-size: 4rem;
+ }
+
+ &.size-4-5rem {
+ font-size: 4.5rem;
+ }
+
+ &.size-5rem {
+ font-size: 5rem;
+ }
+}
+
+/* QUESTO È IL FIX */
+:host ::ng-deep svg {
+ width: 1em;
+ height: 1em;
+ display: block;
+ fill: currentColor;
+}
diff --git a/src/app/ui/shared/icon/icon.ts b/src/app/ui/shared/icon/icon.ts
new file mode 100644
index 00000000..2ba90289
--- /dev/null
+++ b/src/app/ui/shared/icon/icon.ts
@@ -0,0 +1,25 @@
+import { ChangeDetectionStrategy, Component, computed, inject, input } from '@angular/core';
+import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
+import { IconDimension } from '@c_types/icon-dimension.type';
+
+@Component({
+ selector: 'opis-icon',
+ templateUrl: './icon.component.html',
+ styleUrl: './icon.component.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class IconComponent {
+ private readonly _sanitizer = inject(DomSanitizer);
+
+ readonly iconName = input();
+ readonly dimension = input('1-5rem');
+ readonly svgIcon = input();
+
+ protected readonly safeSvgIcon = computed(() => {
+ const svgIcon = this.svgIcon();
+ if (svgIcon) {
+ return this._sanitizer.bypassSecurityTrustHtml(svgIcon);
+ }
+ return null;
+ });
+}
diff --git a/src/app/ui/components/loader/loader.ts b/src/app/ui/shared/loader/loader.ts
similarity index 100%
rename from src/app/ui/components/loader/loader.ts
rename to src/app/ui/shared/loader/loader.ts
diff --git a/src/app/ui/components/logo-animated/logo-animated.html b/src/app/ui/shared/logo-animated/logo-animated.html
similarity index 100%
rename from src/app/ui/components/logo-animated/logo-animated.html
rename to src/app/ui/shared/logo-animated/logo-animated.html
diff --git a/src/app/ui/components/logo-animated/logo-animated.scss b/src/app/ui/shared/logo-animated/logo-animated.scss
similarity index 100%
rename from src/app/ui/components/logo-animated/logo-animated.scss
rename to src/app/ui/shared/logo-animated/logo-animated.scss
diff --git a/src/app/ui/components/logo-animated/logo-animated.ts b/src/app/ui/shared/logo-animated/logo-animated.ts
similarity index 100%
rename from src/app/ui/components/logo-animated/logo-animated.ts
rename to src/app/ui/shared/logo-animated/logo-animated.ts
diff --git a/src/app/values/real-name-contributors.value.ts b/src/app/values/real-name-contributors.value.ts
new file mode 100644
index 00000000..3674db03
--- /dev/null
+++ b/src/app/values/real-name-contributors.value.ts
@@ -0,0 +1,13 @@
+export const REAL_NAME = new Map([
+ ['helias', 'Stefano Borzì'],
+ ['wornairz', 'Alessandro Catalano'],
+ ['pierpaolo791', 'Pierpaolo Pecoraio'],
+ ['', 'Simone Scionti'],
+ ['', 'Alessio Piazza'],
+ ['', 'Diego Sinitò'],
+ ['guberlo', 'Salvo Asero'],
+ ['mkokeshi', 'Giuseppe Ferro'],
+ ['herbrant', 'Davide Carnemolla'],
+ ['makapx', 'Martina Parlavecchio'],
+ ['boozec', 'Santo Cariotti'],
+]);
diff --git a/src/styles.scss b/src/styles.scss
index e9c8ece2..e6e659d9 100644
--- a/src/styles.scss
+++ b/src/styles.scss
@@ -1,11 +1,16 @@
/* You can add global styles to this file, and also import other style files */
@use 'reset';
@use 'palette';
+@use 'scrollbar';
+
+html,
+body {
+ height: 100%;
+ margin: 0;
+}
body {
font-family: var(--font-sans);
- border-top: 2rem solid var(--dark-bg);
- border-bottom: 2rem solid var(--dark-bg);
color: var(--color-text);
}
diff --git a/src/styles/_palette.scss b/src/styles/_palette.scss
index dde4dc92..fd3fc5d8 100644
--- a/src/styles/_palette.scss
+++ b/src/styles/_palette.scss
@@ -39,6 +39,7 @@
/* testo principale */
--gray-700: #333333;
--gray-500: #5f5f5f;
+ --gray-400: #9c9c9c;
/* testo secondario */
--gray-300: #cfcfcf;
--gray-200: #e6e3e0;
diff --git a/src/styles/_scrollbar.scss b/src/styles/_scrollbar.scss
new file mode 100644
index 00000000..7ba08c09
--- /dev/null
+++ b/src/styles/_scrollbar.scss
@@ -0,0 +1,26 @@
+/* Target della scrollbar */
+::-webkit-scrollbar {
+ width: 12px;
+ /* larghezza verticale */
+ height: 12px;
+ /* altezza orizzontale */
+}
+
+/* Track (sfondo della scrollbar) */
+::-webkit-scrollbar-track {
+ background: #ffffff;
+ border-radius: 6px;
+}
+
+/* Thumb (la parte che si muove) */
+::-webkit-scrollbar-thumb {
+ background-color: var(--dark-bg);
+ // border-radius: 6px;
+ // border: 3px solid #f0f0f0;
+ /* crea spazio interno attorno al thumb */
+}
+
+/* Thumb hover */
+::-webkit-scrollbar-thumb:hover {
+ background-color: var(--gray-7100);
+}
diff --git a/tsconfig.json b/tsconfig.json
index 452961b6..00ad673a 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -5,7 +5,7 @@
"compilerOptions": {
"baseUrl": "./",
"paths": {
- "@components/*": ["src/app/ui/components/*"],
+ "@shared-ui/*": ["src/app/ui/shared/*"],
"@sections/*": ["src/app/ui/sections/*"],
"@cards/*": ["src/app/ui/cards/*"],
"@services/*": ["src/app/services/*"],