produzione 1 #9

Merged
JoeKung merged 135 commits from dev into main 2026-03-03 09:58:04 +01:00
11 changed files with 248 additions and 48 deletions
Showing only changes of commit 6f3e601f21 - Show all commits

View File

@@ -10,27 +10,61 @@
<div class="divider"></div>
<p class="description">{{ 'ABOUT.HOW_TEXT' | translate }}</p>
<br>
<h2 class="passions-title">{{ 'ABOUT.PASSIONS_TITLE' | translate }}</h2>
<div class="tags-container">
<span class="tag">{{ 'ABOUT.PILL_1' | translate }}</span>
<span class="tag">{{ 'ABOUT.PILL_2' | translate }}</span>
<span class="tag">{{ 'ABOUT.PILL_3' | translate }}</span>
<span class="tag">{{ 'ABOUT.SERVICE_1' | translate }}</span>
<span class="tag">{{ 'ABOUT.SERVICE_2' | translate }}</span>
@for (passion of passions; track passion.id) {
<span class="tag" [class.is-active]="isPassionActive(passion.id)">
{{ passion.labelKey | translate }}
</span>
}
</div>
</div>
<!-- Right Column: Visuals -->
<div class="visual-content">
<div class="photo-card card-1">
<div class="placeholder-img"></div>
<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">
<span class="member-name">Joe Küng</span>
<span class="member-role">Studente Ingegneria Informatica</span>
</div>
</div>
<div class="photo-card card-2">
<div class="placeholder-img"></div>
<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">
<span class="member-name">Matteo Caletti</span>
<span class="member-role">Studente Ingegneria Elettronica</span>

View File

@@ -29,7 +29,7 @@
text-transform: uppercase;
letter-spacing: 0.15em;
font-size: 0.875rem;
color: var(--color-primary-500);
color: var(--color-secondary-600);
font-weight: 700;
margin-bottom: var(--space-2);
display: block;
@@ -69,7 +69,15 @@ h1 {
font-size: 1.1rem;
line-height: 1.7;
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 {
@@ -92,13 +100,14 @@ h1 {
font-weight: 500;
font-size: 0.9rem;
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 {
transform: translateY(-2px);
border-color: var(--color-primary-500);
color: var(--color-primary-500);
.tag.is-active {
background: var(--color-primary-500);
border-color: var(--color-primary-600);
color: var(--color-neutral-900);
font-weight: 600;
box-shadow: var(--shadow-md);
}
@@ -119,13 +128,37 @@ h1 {
}
.photo-card {
background: var(--color-surface-card);
background: #ffffff;
padding: 1rem;
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
border: 1px solid var(--color-border);
box-shadow: var(--shadow-sm);
width: 100%;
max-width: 260px;
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 {
@@ -134,6 +167,15 @@ h1 {
background: linear-gradient(45deg, var(--color-neutral-200), var(--color-neutral-100));
border-radius: var(--radius-md);
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 {

View File

@@ -2,6 +2,27 @@ import { Component } from '@angular/core';
import { TranslateModule } from '@ngx-translate/core';
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({
selector: 'app-about-page',
standalone: true,
@@ -9,5 +30,71 @@ import { AppLocationsComponent } from '../../shared/components/app-locations/app
templateUrl: './about-page.component.html',
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-bg: #faf9f6;
--color-bg-card: #f7f7f7;
--color-bg-card: #ffffff;
background: var(--home-bg);
}
@@ -228,12 +228,12 @@
.card-image-placeholder {
width: 100%;
height: 160px;
background: var(--color-neutral-100);
background: #f5f5f5;
margin: -1.5rem -1.5rem 1.5rem -1.5rem; /* Negative margins to bleed to edge */
width: calc(100% + 3rem);
border-top-left-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;
align-items: center;
justify-content: center;

View File

@@ -9,11 +9,13 @@
border: 1px solid var(--color-border);
box-shadow: var(--shadow-sm);
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%;
box-sizing: border-box;
&:hover {
transform: translateY(-3px);
box-shadow: var(--shadow-md);
border-color: var(--color-neutral-300);
}
}

View File

@@ -8,7 +8,7 @@
<div class="locations-grid">
<div class="locations-controls">
<div class="location-tabs">
<app-toggle-selector
<app-toggle-selector
[options]="locationOptions"
[selectedValue]="selectedLocation"
(selectionChange)="selectLocation($event)">
@@ -17,14 +17,14 @@
<div class="location-details">
<div *ngIf="selectedLocation === 'ticino'" class="details-card">
<h3>{{ 'LOCATIONS.TICINO' | translate }}</h3>
<h3>{{ 'LOCATIONS.BIASCA' | translate }}</h3>
<p>{{ 'LOCATIONS.ADDRESS_TICINO' | translate }}</p>
</div>
<div *ngIf="selectedLocation === 'bienne'" class="details-card">
<h3>{{ 'LOCATIONS.BIENNE' | translate }}</h3>
<p>{{ 'LOCATIONS.ADDRESS_BIENNE' | translate }}</p>
</div>
<div class="actions">
<a routerLink="/contact" class="contact-btn">
{{ 'LOCATIONS.CONTACT_US' | translate }}
@@ -34,14 +34,14 @@
</div>
<div class="map-container">
<iframe
<iframe
*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">
</iframe>
<iframe
<iframe
*ngIf="selectedLocation === 'bienne'"
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d2729.0438104193587!2d7.240752176735282!3d47.126435979155985!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x478e1eb84efba295%3A0x95924d5ba8b6f3b0!2sLyss-Strasse%2071%2C%202560%20Nidau%2C%20Switzerland!5e0!3m2!1sen!2sch!4v1700000000000!5m2!1sen!2sch"
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d2729.0438104193587!2d7.240752176735282!3d47.126435979155985!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x478e1eb84efba295%3A0x95924d5ba8b6f3b0!2sLyss-Strasse%2071%2C%202560%20Nidau%2C%20Switzerland!5e0!3m2!1sen!2sch!4v1700000000000!5m2!1sen!2sch"
width="100%" height="450" style="border:0;" allowfullscreen="" loading="lazy" referrerpolicy="no-referrer-when-downgrade">
</iframe>
</div>

View File

@@ -29,10 +29,16 @@
align-items: start;
@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 {
margin-bottom: 2rem;
width: 100%;
@@ -81,7 +87,15 @@
border: 1px solid var(--color-border);
box-shadow: var(--shadow-lg);
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 {
width: 100%;

View File

@@ -91,6 +91,20 @@
"SUBTITLE": "Transparency on price, quality and time. Technical and CAD consultation for businesses and individuals.",
"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.",
"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_2": "Technical Consultation",
"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.",
"TICINO": "Ticino",
"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",
"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.",
"SEC_SHOP_TITLE": "Shop",
"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_3": "Supporti e organizzatori per migliorare i flussi di lavoro",
"BTN_DISCOVER": "Scopri i prodotti",
"BTN_REQ_SOLUTION": "Richiedi una soluzione",
"CARD_SHOP_1_TITLE": "Best seller tecnici",
"CARD_SHOP_1_TEXT": "Soluzioni provate sul campo e già pronte alla spedizione.",
"CARD_SHOP_1_TITLE": "Selezione di prodotti verificati",
"CARD_SHOP_1_TEXT": "Controllati per garantire funzionalità e stabilità nel lungo periodo.",
"CARD_SHOP_2_TITLE": "Kit pronti all'uso",
"CARD_SHOP_2_TEXT": "Componenti compatibili e facili da montare senza sorprese.",
"CARD_SHOP_3_TITLE": "Su richiesta",
"CARD_SHOP_3_TEXT": "Non trovi quello che serve? Lo progettiamo e lo produciamo per te.",
"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"
},
"CALC": {
@@ -152,17 +152,23 @@
"ABOUT": {
"TITLE": "Chi Siamo",
"EYEBROW": "Laboratorio di stampa 3D",
"SUBTITLE": "Trasparenza su prezzo, qualità e tempi. Consulenza tecnica e CAD per aziende e privati.",
"HOW_TITLE": "Come lavoriamo",
"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.",
"PILL_1": "Preventivo immediato",
"PILL_2": "Consulenza tecnica",
"PILL_3": "Piccole serie fino a 500 pz",
"SUBTITLE": "Siamo due studenti con tanta voglia di fare e di imparare.",
"HOW_TEXT": "3D fab nasce dall'interesse iniziale di matteo ",
"PASSIONS_TITLE": "Le nostre passioni",
"PASSION_BIKE_TRIAL": "Bike trial",
"PASSION_MOUNTAIN": "Montagna",
"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",
"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_TEXT": "Piccole aziende, freelance, smanettoni e clienti che cercano un prodotto già pronto dallo shop.",
"TEAM_TITLE": "Il Nostro Team"
@@ -171,8 +177,9 @@
"TITLE": "Le Nostre Sedi",
"SUBTITLE": "Siamo presenti in due sedi. Seleziona la sede per vedere i dettagli.",
"TICINO": "Ticino",
"BIASCA": "Biasca",
"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",
"CONTACT_US": "Contattaci"
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 433 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 KiB