feat(frontend): about improvments
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 35s
Build, Test and Deploy / build-and-push (push) Successful in 24s
Build, Test and Deploy / deploy (push) Successful in 9s

This commit is contained in:
2026-02-26 21:42:12 +01:00
parent e82862821e
commit 6f3e601f21
11 changed files with 248 additions and 48 deletions

View File

@@ -10,27 +10,61 @@
<div class="divider"></div> <div class="divider"></div>
<p class="description">{{ 'ABOUT.HOW_TEXT' | translate }}</p> <p class="description">{{ 'ABOUT.HOW_TEXT' | translate }}</p>
<br>
<h2 class="passions-title">{{ 'ABOUT.PASSIONS_TITLE' | translate }}</h2>
<div class="tags-container"> <div class="tags-container">
<span class="tag">{{ 'ABOUT.PILL_1' | translate }}</span> @for (passion of passions; track passion.id) {
<span class="tag">{{ 'ABOUT.PILL_2' | translate }}</span> <span class="tag" [class.is-active]="isPassionActive(passion.id)">
<span class="tag">{{ 'ABOUT.PILL_3' | translate }}</span> {{ passion.labelKey | translate }}
<span class="tag">{{ 'ABOUT.SERVICE_1' | translate }}</span> </span>
<span class="tag">{{ 'ABOUT.SERVICE_2' | translate }}</span> }
</div> </div>
</div> </div>
<!-- Right Column: Visuals --> <!-- Right Column: Visuals -->
<div class="visual-content"> <div class="visual-content">
<div class="photo-card card-1"> <div
<div class="placeholder-img"></div> class="photo-card card-1"
tabindex="0"
role="button"
[attr.aria-pressed]="isMemberSelected('joe')"
[class.is-active]="isMemberActive('joe')"
[class.is-selected]="isMemberSelected('joe')"
(mouseenter)="setHoveredMember('joe')"
(mouseleave)="setHoveredMember(null)"
(focus)="setHoveredMember('joe')"
(blur)="setHoveredMember(null)"
(click)="toggleSelectedMember('joe')"
(keydown.enter)="toggleSelectedMember('joe')"
(keydown.space)="toggleSelectedMember('joe'); $event.preventDefault()"
>
<div class="placeholder-img">
<img src="assets/images/joe.jpg" alt="Joe Küng">
</div>
<div class="member-info"> <div class="member-info">
<span class="member-name">Joe Küng</span> <span class="member-name">Joe Küng</span>
<span class="member-role">Studente Ingegneria Informatica</span> <span class="member-role">Studente Ingegneria Informatica</span>
</div> </div>
</div> </div>
<div class="photo-card card-2"> <div
<div class="placeholder-img"></div> class="photo-card card-2"
tabindex="0"
role="button"
[attr.aria-pressed]="isMemberSelected('matteo')"
[class.is-active]="isMemberActive('matteo')"
[class.is-selected]="isMemberSelected('matteo')"
(mouseenter)="setHoveredMember('matteo')"
(mouseleave)="setHoveredMember(null)"
(focus)="setHoveredMember('matteo')"
(blur)="setHoveredMember(null)"
(click)="toggleSelectedMember('matteo')"
(keydown.enter)="toggleSelectedMember('matteo')"
(keydown.space)="toggleSelectedMember('matteo'); $event.preventDefault()"
>
<div class="placeholder-img">
<img src="assets/images/matteo.jpg" alt="Matteo Caletti">
</div>
<div class="member-info"> <div class="member-info">
<span class="member-name">Matteo Caletti</span> <span class="member-name">Matteo Caletti</span>
<span class="member-role">Studente Ingegneria Elettronica</span> <span class="member-role">Studente Ingegneria Elettronica</span>

View File

@@ -29,7 +29,7 @@
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.15em; letter-spacing: 0.15em;
font-size: 0.875rem; font-size: 0.875rem;
color: var(--color-primary-500); color: var(--color-secondary-600);
font-weight: 700; font-weight: 700;
margin-bottom: var(--space-2); margin-bottom: var(--space-2);
display: block; display: block;
@@ -69,7 +69,15 @@ h1 {
font-size: 1.1rem; font-size: 1.1rem;
line-height: 1.7; line-height: 1.7;
color: var(--color-text-main); color: var(--color-text-main);
margin-bottom: var(--space-8); margin-bottom: var(--space-4);
}
.passions-title {
font-size: 1rem;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--color-secondary-600);
margin-bottom: var(--space-4);
} }
.tags-container { .tags-container {
@@ -92,13 +100,14 @@ h1 {
font-weight: 500; font-weight: 500;
font-size: 0.9rem; font-size: 0.9rem;
box-shadow: var(--shadow-sm); box-shadow: var(--shadow-sm);
transition: all 0.2s ease; transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease, box-shadow 0.2s ease;
} }
.tag:hover { .tag.is-active {
transform: translateY(-2px); background: var(--color-primary-500);
border-color: var(--color-primary-500); border-color: var(--color-primary-600);
color: var(--color-primary-500); color: var(--color-neutral-900);
font-weight: 600;
box-shadow: var(--shadow-md); box-shadow: var(--shadow-md);
} }
@@ -119,13 +128,37 @@ h1 {
} }
.photo-card { .photo-card {
background: var(--color-surface-card); background: #ffffff;
padding: 1rem; padding: 1rem;
border-radius: var(--radius-lg); border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg); border: 1px solid var(--color-border);
box-shadow: var(--shadow-sm);
width: 100%; width: 100%;
max-width: 260px; max-width: 260px;
position: relative; position: relative;
transition: box-shadow 0.2s ease, transform 0.2s ease, border-color 0.2s ease, outline-color 0.2s ease;
cursor: pointer;
outline: 2px solid transparent;
outline-offset: 2px;
}
.photo-card:hover {
transform: translateY(-3px);
box-shadow: var(--shadow-md);
border-color: var(--color-neutral-300);
}
.photo-card:focus-visible {
outline-color: var(--color-primary-600);
}
.photo-card.is-active {
border-color: var(--color-primary-600);
box-shadow: 0 0 0 3px rgb(250 207 10 / 30%), var(--shadow-md);
}
.photo-card.is-selected {
border-color: var(--color-primary-700);
} }
.placeholder-img { .placeholder-img {
@@ -134,6 +167,15 @@ h1 {
background: linear-gradient(45deg, var(--color-neutral-200), var(--color-neutral-100)); background: linear-gradient(45deg, var(--color-neutral-200), var(--color-neutral-100));
border-radius: var(--radius-md); border-radius: var(--radius-md);
margin-bottom: 1rem; margin-bottom: 1rem;
border-bottom: 1px solid var(--color-neutral-300);
overflow: hidden;
}
.placeholder-img img {
width: 100%;
height: 100%;
display: block;
object-fit: cover;
} }
.member-info { .member-info {

View File

@@ -2,6 +2,27 @@ import { Component } from '@angular/core';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { AppLocationsComponent } from '../../shared/components/app-locations/app-locations.component'; import { AppLocationsComponent } from '../../shared/components/app-locations/app-locations.component';
type MemberId = 'joe' | 'matteo';
type PassionId =
| 'bike-trial'
| 'mountain'
| 'ski'
| 'self-hosting'
| 'print-3d'
| 'travel'
| 'snowboard'
| 'snowboard-instructor'
| 'electronics'
| 'woodworking'
| 'van-life'
| 'coffee'
| 'software-development';
interface PassionChip {
id: PassionId;
labelKey: string;
}
@Component({ @Component({
selector: 'app-about-page', selector: 'app-about-page',
standalone: true, standalone: true,
@@ -9,5 +30,71 @@ import { AppLocationsComponent } from '../../shared/components/app-locations/app
templateUrl: './about-page.component.html', templateUrl: './about-page.component.html',
styleUrl: './about-page.component.scss' styleUrl: './about-page.component.scss'
}) })
export class AboutPageComponent {} export class AboutPageComponent {
selectedMember: MemberId | null = null;
hoveredMember: MemberId | null = null;
readonly passions: ReadonlyArray<PassionChip> = [
{ id: 'mountain', labelKey: 'ABOUT.PASSION_MOUNTAIN' },
{ id: 'coffee', labelKey: 'ABOUT.PASSION_COFFEE' },
{ id: 'bike-trial', labelKey: 'ABOUT.PASSION_BIKE_TRIAL' },
{ id: 'electronics', labelKey: 'ABOUT.PASSION_ELECTRONICS' },
{ id: 'travel', labelKey: 'ABOUT.PASSION_TRAVEL' },
{ id: 'woodworking', labelKey: 'ABOUT.PASSION_WOODWORKING' },
{ id: 'print-3d', labelKey: 'ABOUT.PASSION_PRINT_3D' },
{ id: 'ski', labelKey: 'ABOUT.PASSION_SKI' },
{ id: 'software-development', labelKey: 'ABOUT.PASSION_SOFTWARE_DEVELOPMENT' },
{ id: 'snowboard', labelKey: 'ABOUT.PASSION_SNOWBOARD' },
{ id: 'van-life', labelKey: 'ABOUT.PASSION_VAN_LIFE' },
{ id: 'self-hosting', labelKey: 'ABOUT.PASSION_SELF_HOSTING' },
{ id: 'snowboard-instructor', labelKey: 'ABOUT.PASSION_SNOWBOARD_INSTRUCTOR' }
];
private readonly memberPassions: Readonly<Record<MemberId, ReadonlyArray<PassionId>>> = {
joe: [
'bike-trial',
'mountain',
'ski',
'self-hosting',
'print-3d',
'travel',
'coffee',
'software-development'
],
matteo: [
'bike-trial',
'mountain',
'snowboard',
'snowboard-instructor',
'electronics',
'print-3d',
'woodworking',
'van-life'
]
};
get activeMember(): MemberId | null {
return this.hoveredMember ?? this.selectedMember;
}
toggleSelectedMember(member: MemberId): void {
this.selectedMember = this.selectedMember === member ? null : member;
}
setHoveredMember(member: MemberId | null): void {
this.hoveredMember = member;
}
isMemberActive(member: MemberId): boolean {
return this.activeMember === member;
}
isMemberSelected(member: MemberId): boolean {
return this.selectedMember === member;
}
isPassionActive(passionId: PassionId): boolean {
const member = this.activeMember;
return member !== null && this.memberPassions[member].includes(passionId);
}
}

View File

@@ -2,7 +2,7 @@
.home-page { .home-page {
--home-bg: #faf9f6; --home-bg: #faf9f6;
--color-bg-card: #f7f7f7; --color-bg-card: #ffffff;
background: var(--home-bg); background: var(--home-bg);
} }
@@ -228,12 +228,12 @@
.card-image-placeholder { .card-image-placeholder {
width: 100%; width: 100%;
height: 160px; height: 160px;
background: var(--color-neutral-100); background: #f5f5f5;
margin: -1.5rem -1.5rem 1.5rem -1.5rem; /* Negative margins to bleed to edge */ margin: -1.5rem -1.5rem 1.5rem -1.5rem; /* Negative margins to bleed to edge */
width: calc(100% + 3rem); width: calc(100% + 3rem);
border-top-left-radius: var(--radius-lg); border-top-left-radius: var(--radius-lg);
border-top-right-radius: var(--radius-lg); border-top-right-radius: var(--radius-lg);
border-bottom: 1px solid var(--color-border); border-bottom: 1px solid var(--color-neutral-300);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;

View File

@@ -9,11 +9,13 @@
border: 1px solid var(--color-border); border: 1px solid var(--color-border);
box-shadow: var(--shadow-sm); box-shadow: var(--shadow-sm);
padding: var(--space-6); padding: var(--space-6);
transition: box-shadow 0.2s; transition: box-shadow 0.2s ease, transform 0.2s ease, border-color 0.2s ease;
height: 100%; height: 100%;
box-sizing: border-box; box-sizing: border-box;
&:hover { &:hover {
transform: translateY(-3px);
box-shadow: var(--shadow-md); box-shadow: var(--shadow-md);
border-color: var(--color-neutral-300);
} }
} }

View File

@@ -17,7 +17,7 @@
<div class="location-details"> <div class="location-details">
<div *ngIf="selectedLocation === 'ticino'" class="details-card"> <div *ngIf="selectedLocation === 'ticino'" class="details-card">
<h3>{{ 'LOCATIONS.TICINO' | translate }}</h3> <h3>{{ 'LOCATIONS.BIASCA' | translate }}</h3>
<p>{{ 'LOCATIONS.ADDRESS_TICINO' | translate }}</p> <p>{{ 'LOCATIONS.ADDRESS_TICINO' | translate }}</p>
</div> </div>
<div *ngIf="selectedLocation === 'bienne'" class="details-card"> <div *ngIf="selectedLocation === 'bienne'" class="details-card">
@@ -36,7 +36,7 @@
<div class="map-container"> <div class="map-container">
<iframe <iframe
*ngIf="selectedLocation === 'ticino'" *ngIf="selectedLocation === 'ticino'"
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d2753.843657475132!2d8.96627047671755!3d46.35265507912163!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x4785ca88d01d4a03%3A0xc312484ab7592fb!2sVia%20Giov.Bta.%20Pioda%2029A%2C%206710%20Biasca%2C%20Switzerland!5e0!3m2!1sen!2sch!4v1700000000000!5m2!1sen!2sch" src="https://www.google.com/maps?q=Via%20G.%20Pioda%2029a%2C%20Biasca&output=embed"
width="100%" height="450" style="border:0;" allowfullscreen="" loading="lazy" referrerpolicy="no-referrer-when-downgrade"> width="100%" height="450" style="border:0;" allowfullscreen="" loading="lazy" referrerpolicy="no-referrer-when-downgrade">
</iframe> </iframe>
<iframe <iframe

View File

@@ -29,10 +29,16 @@
align-items: start; align-items: start;
@media(min-width: 992px) { @media(min-width: 992px) {
grid-template-columns: 1fr 2fr; grid-template-columns: repeat(2, minmax(320px, 420px));
justify-content: center;
} }
} }
.locations-controls {
width: 100%;
max-width: 420px;
}
.location-tabs { .location-tabs {
margin-bottom: 2rem; margin-bottom: 2rem;
width: 100%; width: 100%;
@@ -81,7 +87,15 @@
border: 1px solid var(--color-border); border: 1px solid var(--color-border);
box-shadow: var(--shadow-lg); box-shadow: var(--shadow-lg);
background: var(--color-bg); background: var(--color-bg);
height: 450px; width: 100%;
max-width: 420px;
justify-self: stretch;
height: 320px;
@media (max-width: 991px) {
max-width: 100%;
height: 300px;
}
iframe { iframe {
width: 100%; width: 100%;

View File

@@ -91,6 +91,20 @@
"SUBTITLE": "Transparency on price, quality and time. Technical and CAD consultation for businesses and individuals.", "SUBTITLE": "Transparency on price, quality and time. Technical and CAD consultation for businesses and individuals.",
"HOW_TITLE": "How we work", "HOW_TITLE": "How we work",
"HOW_TEXT": "We offer an automatic quote for those who already have the 3D file, and a consultation path for those who need to design or optimize the model.", "HOW_TEXT": "We offer an automatic quote for those who already have the 3D file, and a consultation path for those who need to design or optimize the model.",
"PASSIONS_TITLE": "Our passions",
"PASSION_BIKE_TRIAL": "Bike trial",
"PASSION_MOUNTAIN": "Mountain",
"PASSION_SKI": "Ski",
"PASSION_SELF_HOSTING": "Self hosting",
"PASSION_PRINT_3D": "3D printing",
"PASSION_TRAVEL": "Travel",
"PASSION_SNOWBOARD": "Snowboard",
"PASSION_SNOWBOARD_INSTRUCTOR": "Snowboard instructor",
"PASSION_ELECTRONICS": "Electronics",
"PASSION_WOODWORKING": "Woodworking",
"PASSION_VAN_LIFE": "Van life",
"PASSION_COFFEE": "Coffee",
"PASSION_SOFTWARE_DEVELOPMENT": "Software development",
"PILL_1": "Instant Quote", "PILL_1": "Instant Quote",
"PILL_2": "Technical Consultation", "PILL_2": "Technical Consultation",
"PILL_3": "Small series up to 500 pcs", "PILL_3": "Small series up to 500 pcs",
@@ -108,7 +122,7 @@
"SUBTITLE": "We have two locations to serve you better. Select a location to see details.", "SUBTITLE": "We have two locations to serve you better. Select a location to see details.",
"TICINO": "Ticino", "TICINO": "Ticino",
"BIENNE": "Bienne", "BIENNE": "Bienne",
"ADDRESS_TICINO": "Via G. Pioda 29a, 6710 Biasca (TI)", "ADDRESS_TICINO": "Via G. Pioda 29a, Biasca",
"ADDRESS_BIENNE": "Lyss-strasse 71, Nidau 2560 Bienne", "ADDRESS_BIENNE": "Lyss-strasse 71, Nidau 2560 Bienne",
"CONTACT_US": "Contact Us" "CONTACT_US": "Contact Us"
}, },

View File

@@ -41,19 +41,19 @@
"CAP_4_TEXT": "Non hai il file 3D? Ti aiutiamo a progettarlo, ottimizzarlo per la stampa e scegliere il materiale giusto insieme.", "CAP_4_TEXT": "Non hai il file 3D? Ti aiutiamo a progettarlo, ottimizzarlo per la stampa e scegliere il materiale giusto insieme.",
"SEC_SHOP_TITLE": "Shop", "SEC_SHOP_TITLE": "Shop",
"SEC_SHOP_TEXT": "Prodotti selezionati, e pronti all'uso.", "SEC_SHOP_TEXT": "Prodotti selezionati, e pronti all'uso.",
"SEC_SHOP_LIST_1": "Accessori funzionali per officine e laboratori", "SEC_SHOP_LIST_1": "Prodotti disegnati da noi",
"SEC_SHOP_LIST_2": "Ricambi e componenti difficili da reperire", "SEC_SHOP_LIST_2": "Ricambi e componenti difficili da reperire",
"SEC_SHOP_LIST_3": "Supporti e organizzatori per migliorare i flussi di lavoro", "SEC_SHOP_LIST_3": "Supporti e organizzatori per migliorare i flussi di lavoro",
"BTN_DISCOVER": "Scopri i prodotti", "BTN_DISCOVER": "Scopri i prodotti",
"BTN_REQ_SOLUTION": "Richiedi una soluzione", "BTN_REQ_SOLUTION": "Richiedi una soluzione",
"CARD_SHOP_1_TITLE": "Best seller tecnici", "CARD_SHOP_1_TITLE": "Selezione di prodotti verificati",
"CARD_SHOP_1_TEXT": "Soluzioni provate sul campo e già pronte alla spedizione.", "CARD_SHOP_1_TEXT": "Controllati per garantire funzionalità e stabilità nel lungo periodo.",
"CARD_SHOP_2_TITLE": "Kit pronti all'uso", "CARD_SHOP_2_TITLE": "Kit pronti all'uso",
"CARD_SHOP_2_TEXT": "Componenti compatibili e facili da montare senza sorprese.", "CARD_SHOP_2_TEXT": "Componenti compatibili e facili da montare senza sorprese.",
"CARD_SHOP_3_TITLE": "Su richiesta", "CARD_SHOP_3_TITLE": "Su richiesta",
"CARD_SHOP_3_TEXT": "Non trovi quello che serve? Lo progettiamo e lo produciamo per te.", "CARD_SHOP_3_TEXT": "Non trovi quello che serve? Lo progettiamo e lo produciamo per te.",
"SEC_ABOUT_TITLE": "Su di noi", "SEC_ABOUT_TITLE": "Su di noi",
"SEC_ABOUT_TEXT": "3D fab è un laboratorio tecnico di stampa 3D. Seguiamo progetti dalla consulenza iniziale alla produzione, con tempi chiari e supporto diretto.", "SEC_ABOUT_TEXT": "Siamo due studenti di ingegneria: la stampa 3D ci ha conquistati per un motivo semplice, vedere un problema e costruire la soluzione. Da questa idea prendono forma prototipi, componenti oggetti pensati per funzionare nella quotidianità. ",
"FOUNDERS_PHOTO": "Foto Founders" "FOUNDERS_PHOTO": "Foto Founders"
}, },
"CALC": { "CALC": {
@@ -152,17 +152,23 @@
"ABOUT": { "ABOUT": {
"TITLE": "Chi Siamo", "TITLE": "Chi Siamo",
"EYEBROW": "Laboratorio di stampa 3D", "EYEBROW": "Laboratorio di stampa 3D",
"SUBTITLE": "Trasparenza su prezzo, qualità e tempi. Consulenza tecnica e CAD per aziende e privati.", "SUBTITLE": "Siamo due studenti con tanta voglia di fare e di imparare.",
"HOW_TITLE": "Come lavoriamo", "HOW_TEXT": "3D fab nasce dall'interesse iniziale di matteo ",
"HOW_TEXT": "Offriamo un preventivo automatico per chi ha già il file 3D, e un percorso di consulenza per chi deve progettare o ottimizzare il modello.", "PASSIONS_TITLE": "Le nostre passioni",
"PILL_1": "Preventivo immediato", "PASSION_BIKE_TRIAL": "Bike trial",
"PILL_2": "Consulenza tecnica", "PASSION_MOUNTAIN": "Montagna",
"PILL_3": "Piccole serie fino a 500 pz", "PASSION_SKI": "Ski",
"PASSION_SELF_HOSTING": "Self hosting",
"PASSION_PRINT_3D": "Stampa 3D",
"PASSION_TRAVEL": "Travel",
"PASSION_SNOWBOARD": "Snowboard",
"PASSION_SNOWBOARD_INSTRUCTOR": "Istruttore snowboard",
"PASSION_ELECTRONICS": "Elettronica",
"PASSION_WOODWORKING": "Lavorazione del legno",
"PASSION_VAN_LIFE": "Van life",
"PASSION_COFFEE": "Caffè",
"PASSION_SOFTWARE_DEVELOPMENT": "Sviluppo software",
"SERVICES_TITLE": "Servizi principali", "SERVICES_TITLE": "Servizi principali",
"SERVICE_1": "Stampa 3D FDM per prototipi e piccole serie",
"SERVICE_2": "Materiali tecnici su richiesta",
"SERVICE_3": "Supporto CAD e post-processing",
"SERVICE_4": "Verifica file e ottimizzazione per la stampa",
"TARGET_TITLE": "Per chi è", "TARGET_TITLE": "Per chi è",
"TARGET_TEXT": "Piccole aziende, freelance, smanettoni e clienti che cercano un prodotto già pronto dallo shop.", "TARGET_TEXT": "Piccole aziende, freelance, smanettoni e clienti che cercano un prodotto già pronto dallo shop.",
"TEAM_TITLE": "Il Nostro Team" "TEAM_TITLE": "Il Nostro Team"
@@ -171,8 +177,9 @@
"TITLE": "Le Nostre Sedi", "TITLE": "Le Nostre Sedi",
"SUBTITLE": "Siamo presenti in due sedi. Seleziona la sede per vedere i dettagli.", "SUBTITLE": "Siamo presenti in due sedi. Seleziona la sede per vedere i dettagli.",
"TICINO": "Ticino", "TICINO": "Ticino",
"BIASCA": "Biasca",
"BIENNE": "Bienne", "BIENNE": "Bienne",
"ADDRESS_TICINO": "Via G. Pioda 29a, 6710 Biasca (TI)", "ADDRESS_TICINO": "Via G. Pioda 29a, Biasca",
"ADDRESS_BIENNE": "Lyss-strasse 71, Nidau 2560 Bienne", "ADDRESS_BIENNE": "Lyss-strasse 71, Nidau 2560 Bienne",
"CONTACT_US": "Contattaci" "CONTACT_US": "Contattaci"
}, },

Binary file not shown.

After

Width:  |  Height:  |  Size: 433 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 KiB